Compare commits
3 Commits
1.0.0-pre.
...
1.0.0-pre.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f7a30d285 | ||
|
|
5b1fc203ed | ||
|
|
add668dfd2 |
74
CHANGELOG.md
74
CHANGELOG.md
@@ -6,7 +6,73 @@ 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).
|
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||||
|
|
||||||
## [1.0.0-pre.7] - 2022-04-01
|
## [1.0.0-pre.10] - 2022-06-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
|
||||||
|
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
|
||||||
|
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
|
||||||
|
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
|
||||||
|
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
|
||||||
|
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
|
||||||
|
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
|
||||||
|
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
|
||||||
|
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
|
||||||
|
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
|
||||||
|
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
|
||||||
|
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
|
||||||
|
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
|
||||||
|
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
|
||||||
|
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
|
||||||
|
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
|
||||||
|
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
|
||||||
|
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
|
||||||
|
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
|
||||||
|
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
|
||||||
|
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
|
||||||
|
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
|
||||||
|
|
||||||
|
## [1.0.0-pre.9] - 2022-05-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Hosting again after failing to host now works correctly (#1938)
|
||||||
|
- Fixed NetworkManager to cleanup connected client lists after stopping (#1945)
|
||||||
|
- Fixed NetworkHide followed by NetworkShow on the same frame works correctly (#1940)
|
||||||
|
|
||||||
|
## [1.0.0-pre.8] - 2022-04-27
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `unmanaged` structs are no longer universally accepted as RPC parameters because some structs (i.e., structs with pointers in them, such as `NativeList<T>`) can't be supported by the default memcpy struct serializer. Structs that are intended to be serialized across the network must add `INetworkSerializeByMemcpy` to the interface list (i.e., `struct Foo : INetworkSerializeByMemcpy`). This interface is empty and just serves to mark the struct as compatible with memcpy serialization. For external structs you can't edit, you can pass them to RPCs by wrapping them in `ForceNetworkSerializeByMemcpy<T>`. (#1901)
|
||||||
|
- Changed requirement to register in-scene placed NetworkObjects with `NetworkManager` in order to respawn them. In-scene placed NetworkObjects are now automatically tracked during runtime and no longer need to be registered as a NetworkPrefab. (#1898)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed `SIPTransport` (#1870)
|
||||||
|
- Removed `ClientNetworkTransform` from the package samples and moved to Boss Room's Utilities package which can be found [here](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs) (#1912)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue where `NetworkSceneManager` did not synchronize despawned in-scene placed NetworkObjects. (#1898)
|
||||||
|
- Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890)
|
||||||
|
- Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884)
|
||||||
|
- Fixed issue during client synchronization if 'ValidateSceneBeforeLoading' returned false it would halt the client synchronization process resulting in a client that was approved but not synchronized or fully connected with the server. (#1883)
|
||||||
|
- Fixed an issue where UNetTransport.StartServer would return success even if the underlying transport failed to start (#854)
|
||||||
|
- Passing generic types to RPCs no longer causes a native crash (#1901)
|
||||||
|
- Fixed a compile failure when compiling against com.unity.nuget.mono-cecil >= 1.11.4 (#1920)
|
||||||
|
- Fixed an issue where calling `Shutdown` on a `NetworkManager` that was already shut down would cause an immediate shutdown the next time it was started (basically the fix makes `Shutdown` idempotent). (#1877)
|
||||||
|
|
||||||
|
## [1.0.0-pre.7] - 2022-04-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -15,9 +81,12 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
|||||||
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)
|
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)
|
||||||
- `UnityTransport` settings can now be set programmatically. (#1845)
|
- `UnityTransport` settings can now be set programmatically. (#1845)
|
||||||
- `FastBufferWriter` and Reader IsInitialized property. (#1859)
|
- `FastBufferWriter` and Reader IsInitialized property. (#1859)
|
||||||
|
- Prefabs can now be added to the network at **runtime** (i.e., from an addressable asset). If `ForceSamePrefabs` is false, this can happen after a connection has been formed. (#1882)
|
||||||
|
- When `ForceSamePrefabs` is false, a configurable delay (default 1 second, configurable via `NetworkConfig.SpawnTimeout`) has been introduced to gracefully handle race conditions where a spawn call has been received for an object whose prefab is still being loaded. (#1882)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Changed `NetcodeIntegrationTestHelpers` to use `UnityTransport` (#1870)
|
||||||
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849)
|
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
@@ -27,6 +96,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
|||||||
- Removed `com.unity.collections` dependency from the package (#1849)
|
- Removed `com.unity.collections` dependency from the package (#1849)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed in-scene placed NetworkObjects not being found/ignored after a client disconnects and then reconnects. (#1850)
|
- Fixed in-scene placed NetworkObjects not being found/ignored after a client disconnects and then reconnects. (#1850)
|
||||||
- Fixed issue where `UnityTransport` send queues were not flushed when calling `DisconnectLocalClient` or `DisconnectRemoteClient`. (#1847)
|
- Fixed issue where `UnityTransport` send queues were not flushed when calling `DisconnectLocalClient` or `DisconnectRemoteClient`. (#1847)
|
||||||
- Fixed NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
|
- Fixed NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
|
||||||
@@ -49,8 +119,6 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
|||||||
- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765)
|
- 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)
|
- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735)
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
### Fixed
|
### 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 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)
|
- 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)
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Solves for incoming values that are jittered
|
/// Solves for incoming values that are jittered
|
||||||
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
|
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
public abstract class BufferedLinearInterpolator<T> where T : struct
|
||||||
internal abstract class BufferedLinearInterpolator<T> where T : struct
|
|
||||||
{
|
{
|
||||||
|
internal float MaxInterpolationBound = 3.0f;
|
||||||
private struct BufferedItem
|
private struct BufferedItem
|
||||||
{
|
{
|
||||||
public T Item;
|
public T Item;
|
||||||
@@ -24,6 +23,10 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// There’s two factors affecting interpolation: buffering (set in NetworkManager’s NetworkTimeSystem) and interpolation time, which is the amount of time it’ll take to reach the target. This is to affect the second one.
|
||||||
|
/// </summary>
|
||||||
|
public float MaximumInterpolationTime = 0.1f;
|
||||||
|
|
||||||
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
|
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
|
||||||
|
|
||||||
@@ -69,6 +72,9 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
|
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets Interpolator to initial state
|
||||||
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
m_Buffer.Clear();
|
m_Buffer.Clear();
|
||||||
@@ -76,6 +82,9 @@ namespace Unity.Netcode
|
|||||||
m_StartTimeConsumed = 0.0d;
|
m_StartTimeConsumed = 0.0d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Teleports current interpolation value to targetValue.
|
||||||
|
/// </summary>
|
||||||
public void ResetTo(T targetValue, double serverTime)
|
public void ResetTo(T targetValue, double serverTime)
|
||||||
{
|
{
|
||||||
m_LifetimeConsumedCount = 1;
|
m_LifetimeConsumedCount = 1;
|
||||||
@@ -89,7 +98,6 @@ namespace Unity.Netcode
|
|||||||
Update(0, serverTime, serverTime);
|
Update(0, serverTime, serverTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
|
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
|
||||||
private void TryConsumeFromBuffer(double renderTime, double serverTime)
|
private void TryConsumeFromBuffer(double renderTime, double serverTime)
|
||||||
{
|
{
|
||||||
@@ -196,23 +204,24 @@ namespace Unity.Netcode
|
|||||||
t = 0.0f;
|
t = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t > 3.0f) // max extrapolation
|
if (t > MaxInterpolationBound) // max extrapolation
|
||||||
{
|
{
|
||||||
// TODO this causes issues with teleport, investigate
|
// TODO this causes issues with teleport, investigate
|
||||||
// todo make this configurable
|
|
||||||
t = 1.0f;
|
t = 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
|
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
|
||||||
float maxInterpTime = 0.1f;
|
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps
|
||||||
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / maxInterpTime); // second interpolate to smooth out extrapolation jumps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_NbItemsReceivedThisFrame = 0;
|
m_NbItemsReceivedThisFrame = 0;
|
||||||
return m_CurrentInterpValue;
|
return m_CurrentInterpValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
|
||||||
|
/// </summary>
|
||||||
public void AddMeasurement(T newMeasurement, double sentTime)
|
public void AddMeasurement(T newMeasurement, double sentTime)
|
||||||
{
|
{
|
||||||
m_NbItemsReceivedThisFrame++;
|
m_NbItemsReceivedThisFrame++;
|
||||||
@@ -239,17 +248,25 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets latest value from the interpolator. This is updated every update as time goes by.
|
||||||
|
/// </summary>
|
||||||
public T GetInterpolatedValue()
|
public T GetInterpolatedValue()
|
||||||
{
|
{
|
||||||
return m_CurrentInterpValue;
|
return m_CurrentInterpValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped.
|
||||||
|
/// </summary>
|
||||||
protected abstract T Interpolate(T start, T end, float time);
|
protected abstract T Interpolate(T start, T end, float time);
|
||||||
|
/// <summary>
|
||||||
|
/// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped.
|
||||||
|
/// </summary>
|
||||||
protected abstract T InterpolateUnclamped(T start, T end, float time);
|
protected abstract T InterpolateUnclamped(T start, T end, float time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
||||||
internal class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
|
||||||
{
|
{
|
||||||
protected override float InterpolateUnclamped(float start, float end, float time)
|
protected override float InterpolateUnclamped(float start, float end, float time)
|
||||||
{
|
{
|
||||||
@@ -262,7 +279,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||||
{
|
{
|
||||||
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,157 @@
|
|||||||
#if COM_UNITY_MODULES_ANIMATION
|
#if COM_UNITY_MODULES_ANIMATION
|
||||||
|
using System.Collections.Generic;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Collections.LowLevel.Unsafe;
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode.Components
|
namespace Unity.Netcode.Components
|
||||||
{
|
{
|
||||||
|
internal class NetworkAnimatorStateChangeHandler : INetworkUpdateSystem
|
||||||
|
{
|
||||||
|
private NetworkAnimator m_NetworkAnimator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This removes sending RPCs from within RPCs when the
|
||||||
|
/// server is forwarding updates from clients to clients
|
||||||
|
/// As well this handles newly connected client synchronization
|
||||||
|
/// of the existing Animator's state.
|
||||||
|
/// </summary>
|
||||||
|
private void FlushMessages()
|
||||||
|
{
|
||||||
|
foreach (var clientId in m_ClientsToSynchronize)
|
||||||
|
{
|
||||||
|
m_NetworkAnimator.ServerSynchronizeNewPlayer(clientId);
|
||||||
|
}
|
||||||
|
m_ClientsToSynchronize.Clear();
|
||||||
|
|
||||||
|
foreach (var sendEntry in m_SendParameterUpdates)
|
||||||
|
{
|
||||||
|
m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams);
|
||||||
|
}
|
||||||
|
m_SendParameterUpdates.Clear();
|
||||||
|
|
||||||
|
foreach (var sendEntry in m_SendTriggerUpdates)
|
||||||
|
{
|
||||||
|
m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams);
|
||||||
|
}
|
||||||
|
m_SendTriggerUpdates.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NetworkUpdate(NetworkUpdateStage updateStage)
|
||||||
|
{
|
||||||
|
switch (updateStage)
|
||||||
|
{
|
||||||
|
case NetworkUpdateStage.PreUpdate:
|
||||||
|
{
|
||||||
|
// Only the server forwards messages and synchronizes players
|
||||||
|
if (m_NetworkAnimator.NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
// Flush any pending messages
|
||||||
|
FlushMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everyone applies any parameters updated
|
||||||
|
foreach (var parameterUpdate in m_ProcessParameterUpdates)
|
||||||
|
{
|
||||||
|
m_NetworkAnimator.UpdateParameters(parameterUpdate);
|
||||||
|
}
|
||||||
|
m_ProcessParameterUpdates.Clear();
|
||||||
|
|
||||||
|
// Only owners check for Animator changes
|
||||||
|
if (m_NetworkAnimator.IsOwner && !m_NetworkAnimator.IsServerAuthoritative() || m_NetworkAnimator.IsServerAuthoritative() && m_NetworkAnimator.NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
m_NetworkAnimator.CheckForAnimatorChanges();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clients that need to be synchronized to the relative Animator
|
||||||
|
/// </summary>
|
||||||
|
private List<ulong> m_ClientsToSynchronize = new List<ulong>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When a new client is connected, they are added to the
|
||||||
|
/// m_ClientsToSynchronize list.
|
||||||
|
/// </summary>
|
||||||
|
internal void SynchronizeClient(ulong clientId)
|
||||||
|
{
|
||||||
|
m_ClientsToSynchronize.Add(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A pending outgoing Animation update for (n) clients
|
||||||
|
/// </summary>
|
||||||
|
private struct AnimationUpdate
|
||||||
|
{
|
||||||
|
public ClientRpcParams ClientRpcParams;
|
||||||
|
public NetworkAnimator.AnimationMessage AnimationMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AnimationUpdate> m_SendAnimationUpdates = new List<AnimationUpdate>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a server needs to forwarding an update to the animation state
|
||||||
|
/// </summary>
|
||||||
|
internal void SendAnimationUpdate(NetworkAnimator.AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
|
||||||
|
{
|
||||||
|
m_SendAnimationUpdates.Add(new AnimationUpdate() { ClientRpcParams = clientRpcParams, AnimationMessage = animationMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ParameterUpdate
|
||||||
|
{
|
||||||
|
public ClientRpcParams ClientRpcParams;
|
||||||
|
public NetworkAnimator.ParametersUpdateMessage ParametersUpdateMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ParameterUpdate> m_SendParameterUpdates = new List<ParameterUpdate>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a server needs to forwarding an update to the parameter state
|
||||||
|
/// </summary>
|
||||||
|
internal void SendParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage, ClientRpcParams clientRpcParams = default)
|
||||||
|
{
|
||||||
|
m_SendParameterUpdates.Add(new ParameterUpdate() { ClientRpcParams = clientRpcParams, ParametersUpdateMessage = parametersUpdateMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<NetworkAnimator.ParametersUpdateMessage> m_ProcessParameterUpdates = new List<NetworkAnimator.ParametersUpdateMessage>();
|
||||||
|
internal void ProcessParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage)
|
||||||
|
{
|
||||||
|
m_ProcessParameterUpdates.Add(parametersUpdateMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TriggerUpdate
|
||||||
|
{
|
||||||
|
public ClientRpcParams ClientRpcParams;
|
||||||
|
public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TriggerUpdate> m_SendTriggerUpdates = new List<TriggerUpdate>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a server needs to forward an update to a Trigger state
|
||||||
|
/// </summary>
|
||||||
|
internal void SendTriggerUpdate(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
|
||||||
|
{
|
||||||
|
m_SendTriggerUpdates.Add(new TriggerUpdate() { ClientRpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DeregisterUpdate()
|
||||||
|
{
|
||||||
|
NetworkUpdateLoop.UnregisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator)
|
||||||
|
{
|
||||||
|
m_NetworkAnimator = networkAnimator;
|
||||||
|
NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// NetworkAnimator enables remote synchronization of <see cref="UnityEngine.Animator"/> state for on network objects.
|
/// NetworkAnimator enables remote synchronization of <see cref="UnityEngine.Animator"/> state for on network objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -15,11 +162,10 @@ namespace Unity.Netcode.Components
|
|||||||
internal struct AnimationMessage : INetworkSerializable
|
internal struct AnimationMessage : INetworkSerializable
|
||||||
{
|
{
|
||||||
// state hash per layer. 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;
|
internal int StateHash;
|
||||||
public float NormalizedTime;
|
internal float NormalizedTime;
|
||||||
public int Layer;
|
internal int Layer;
|
||||||
public float Weight;
|
internal float Weight;
|
||||||
public byte[] Parameters;
|
|
||||||
|
|
||||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||||
{
|
{
|
||||||
@@ -27,19 +173,27 @@ namespace Unity.Netcode.Components
|
|||||||
serializer.SerializeValue(ref NormalizedTime);
|
serializer.SerializeValue(ref NormalizedTime);
|
||||||
serializer.SerializeValue(ref Layer);
|
serializer.SerializeValue(ref Layer);
|
||||||
serializer.SerializeValue(ref Weight);
|
serializer.SerializeValue(ref Weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ParametersUpdateMessage : INetworkSerializable
|
||||||
|
{
|
||||||
|
internal byte[] Parameters;
|
||||||
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||||
|
{
|
||||||
serializer.SerializeValue(ref Parameters);
|
serializer.SerializeValue(ref Parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct AnimationTriggerMessage : INetworkSerializable
|
internal struct AnimationTriggerMessage : INetworkSerializable
|
||||||
{
|
{
|
||||||
public int Hash;
|
internal int Hash;
|
||||||
public bool Reset;
|
internal bool IsTriggerSet;
|
||||||
|
|
||||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||||
{
|
{
|
||||||
serializer.SerializeValue(ref Hash);
|
serializer.SerializeValue(ref Hash);
|
||||||
serializer.SerializeValue(ref Reset);
|
serializer.SerializeValue(ref IsTriggerSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,66 +208,126 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool m_SendMessagesAllowed = false;
|
internal bool IsServerAuthoritative()
|
||||||
|
{
|
||||||
|
return OnIsServerAuthoritative();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override this method and return false to switch to owner authoritative mode
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool OnIsServerAuthoritative()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Animators only support up to 32 params
|
// Animators only support up to 32 params
|
||||||
public static int K_MaxAnimationParams = 32;
|
private const int k_MaxAnimationParams = 32;
|
||||||
|
|
||||||
private int[] m_TransitionHash;
|
private int[] m_TransitionHash;
|
||||||
private int[] m_AnimationHash;
|
private int[] m_AnimationHash;
|
||||||
private float[] m_LayerWeights;
|
private float[] m_LayerWeights;
|
||||||
|
private static byte[] s_EmptyArray = new byte[] { };
|
||||||
|
private NetworkAnimatorStateChangeHandler m_NetworkAnimatorStateChangeHandler;
|
||||||
|
|
||||||
private unsafe struct AnimatorParamCache
|
private unsafe struct AnimatorParamCache
|
||||||
{
|
{
|
||||||
public int Hash;
|
internal int Hash;
|
||||||
public int Type;
|
internal int Type;
|
||||||
public fixed byte Value[4]; // this is a max size of 4 bytes
|
internal fixed byte Value[4]; // this is a max size of 4 bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// 128 bytes per Animator
|
// 128 bytes per Animator
|
||||||
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(K_MaxAnimationParams * sizeof(float), Allocator.Persistent);
|
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(k_MaxAnimationParams * sizeof(float), Allocator.Persistent);
|
||||||
|
|
||||||
private NativeArray<AnimatorParamCache> m_CachedAnimatorParameters;
|
private NativeArray<AnimatorParamCache> m_CachedAnimatorParameters;
|
||||||
|
|
||||||
// We cache these values because UnsafeUtility.EnumToInt uses direct IL that allows a non-boxing conversion
|
// We cache these values because UnsafeUtility.EnumToInt uses direct IL that allows a non-boxing conversion
|
||||||
private struct AnimationParamEnumWrapper
|
private struct AnimationParamEnumWrapper
|
||||||
{
|
{
|
||||||
public static readonly int AnimatorControllerParameterInt;
|
internal static readonly int AnimatorControllerParameterInt;
|
||||||
public static readonly int AnimatorControllerParameterFloat;
|
internal static readonly int AnimatorControllerParameterFloat;
|
||||||
public static readonly int AnimatorControllerParameterBool;
|
internal static readonly int AnimatorControllerParameterBool;
|
||||||
|
internal static readonly int AnimatorControllerParameterTriggerBool;
|
||||||
|
|
||||||
static AnimationParamEnumWrapper()
|
static AnimationParamEnumWrapper()
|
||||||
{
|
{
|
||||||
AnimatorControllerParameterInt = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Int);
|
AnimatorControllerParameterInt = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Int);
|
||||||
AnimatorControllerParameterFloat = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Float);
|
AnimatorControllerParameterFloat = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Float);
|
||||||
AnimatorControllerParameterBool = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Bool);
|
AnimatorControllerParameterBool = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Bool);
|
||||||
|
AnimatorControllerParameterTriggerBool = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cleanup()
|
||||||
|
{
|
||||||
|
if (m_NetworkAnimatorStateChangeHandler != null)
|
||||||
|
{
|
||||||
|
m_NetworkAnimatorStateChangeHandler.DeregisterUpdate();
|
||||||
|
m_NetworkAnimatorStateChangeHandler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
NetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated)
|
||||||
|
{
|
||||||
|
m_CachedAnimatorParameters.Dispose();
|
||||||
|
}
|
||||||
|
if (m_ParameterWriter.IsInitialized)
|
||||||
|
{
|
||||||
|
m_ParameterWriter.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnDestroy()
|
public override void OnDestroy()
|
||||||
{
|
{
|
||||||
if (m_CachedAnimatorParameters.IsCreated)
|
Cleanup();
|
||||||
{
|
base.OnDestroy();
|
||||||
m_CachedAnimatorParameters.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ParameterWriter.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<int> m_ParametersToUpdate;
|
||||||
|
private List<ulong> m_ClientSendList;
|
||||||
|
private ClientRpcParams m_ClientRpcParams;
|
||||||
|
|
||||||
public override void OnNetworkSpawn()
|
public override void OnNetworkSpawn()
|
||||||
{
|
{
|
||||||
if (IsServer)
|
if (IsOwner || IsServer)
|
||||||
{
|
{
|
||||||
m_SendMessagesAllowed = true;
|
|
||||||
int layers = m_Animator.layerCount;
|
int layers = m_Animator.layerCount;
|
||||||
|
|
||||||
m_TransitionHash = new int[layers];
|
m_TransitionHash = new int[layers];
|
||||||
m_AnimationHash = new int[layers];
|
m_AnimationHash = new int[layers];
|
||||||
m_LayerWeights = new float[layers];
|
m_LayerWeights = new float[layers];
|
||||||
|
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store off our current layer weights
|
||||||
|
for (int layer = 0; layer < m_Animator.layerCount; layer++)
|
||||||
|
{
|
||||||
|
float layerWeightNow = m_Animator.GetLayerWeight(layer);
|
||||||
|
if (layerWeightNow != m_LayerWeights[layer])
|
||||||
|
{
|
||||||
|
m_LayerWeights[layer] = layerWeightNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
m_ClientSendList = new List<ulong>(128);
|
||||||
|
m_ClientRpcParams = new ClientRpcParams();
|
||||||
|
m_ClientRpcParams.Send = new ClientRpcSendParams();
|
||||||
|
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var parameters = m_Animator.parameters;
|
var parameters = m_Animator.parameters;
|
||||||
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
|
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
|
||||||
|
m_ParametersToUpdate = new List<int>(parameters.Length);
|
||||||
for (var i = 0; i < parameters.Length; i++)
|
for (var i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
var parameter = parameters[i];
|
var parameter = parameters[i];
|
||||||
@@ -142,13 +356,11 @@ namespace Unity.Netcode.Components
|
|||||||
case AnimatorControllerParameterType.Int:
|
case AnimatorControllerParameterType.Int:
|
||||||
var valueInt = m_Animator.GetInteger(cacheParam.Hash);
|
var valueInt = m_Animator.GetInteger(cacheParam.Hash);
|
||||||
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueInt);
|
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueInt);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case AnimatorControllerParameterType.Bool:
|
case AnimatorControllerParameterType.Bool:
|
||||||
var valueBool = m_Animator.GetBool(cacheParam.Hash);
|
var valueBool = m_Animator.GetBool(cacheParam.Hash);
|
||||||
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueBool);
|
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueBool);
|
||||||
break;
|
break;
|
||||||
case AnimatorControllerParameterType.Trigger:
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -156,24 +368,117 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
m_CachedAnimatorParameters[i] = cacheParam;
|
m_CachedAnimatorParameters[i] = cacheParam;
|
||||||
}
|
}
|
||||||
|
m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNetworkDespawn()
|
public override void OnNetworkDespawn()
|
||||||
{
|
{
|
||||||
m_SendMessagesAllowed = false;
|
Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FixedUpdate()
|
/// <summary>
|
||||||
|
/// Synchronizes newly joined players
|
||||||
|
/// </summary>
|
||||||
|
internal void ServerSynchronizeNewPlayer(ulong playerId)
|
||||||
{
|
{
|
||||||
if (!m_SendMessagesAllowed || !m_Animator || !m_Animator.enabled)
|
m_ClientSendList.Clear();
|
||||||
|
m_ClientSendList.Add(playerId);
|
||||||
|
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
||||||
|
// With synchronization we send all parameters
|
||||||
|
m_ParametersToUpdate.Clear();
|
||||||
|
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
||||||
|
{
|
||||||
|
m_ParametersToUpdate.Add(i);
|
||||||
|
}
|
||||||
|
SendParametersUpdate(m_ClientRpcParams);
|
||||||
|
for (int layer = 0; layer < m_Animator.layerCount; layer++)
|
||||||
|
{
|
||||||
|
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
|
||||||
|
|
||||||
|
var stateHash = st.fullPathHash;
|
||||||
|
var normalizedTime = st.normalizedTime;
|
||||||
|
var totalSpeed = st.speed * st.speedMultiplier;
|
||||||
|
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
|
||||||
|
// NOTE:
|
||||||
|
// When synchronizing, for now we will just complete the transition and
|
||||||
|
// synchronize the player to the next state being transitioned into
|
||||||
|
if (m_Animator.IsInTransition(layer))
|
||||||
|
{
|
||||||
|
var tt = m_Animator.GetAnimatorTransitionInfo(layer);
|
||||||
|
var nextState = m_Animator.GetNextAnimatorStateInfo(layer);
|
||||||
|
|
||||||
|
if (nextState.length > 0)
|
||||||
|
{
|
||||||
|
var nextStateTotalSpeed = nextState.speed * nextState.speedMultiplier;
|
||||||
|
var nextStateAdjustedLength = nextState.length * nextStateTotalSpeed;
|
||||||
|
// TODO: We need to get the transition curve for the target state as well as some
|
||||||
|
// reasonable RTT estimate in order to get a more precise normalized synchronization time
|
||||||
|
var transitionTime = Mathf.Min(tt.duration, tt.duration * tt.normalizedTime) * 0.5f;
|
||||||
|
normalizedTime = Mathf.Min(1.0f, transitionTime > 0.0f ? transitionTime / nextStateAdjustedLength : 0.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
normalizedTime = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
stateHash = nextState.fullPathHash;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (st.normalizedTime >= adjustedNormalizedMaxTime)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var animMsg = new AnimationMessage
|
||||||
|
{
|
||||||
|
StateHash = stateHash,
|
||||||
|
NormalizedTime = normalizedTime,
|
||||||
|
Layer = layer,
|
||||||
|
Weight = m_LayerWeights[layer]
|
||||||
|
};
|
||||||
|
// Server always send via client RPC
|
||||||
|
SendAnimStateClientRpc(animMsg, m_ClientRpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClientConnectedCallback(ulong playerId)
|
||||||
|
{
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CheckForAnimatorChanges()
|
||||||
|
{
|
||||||
|
if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CheckParametersChanged())
|
||||||
|
{
|
||||||
|
SendParametersUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Animator.runtimeAnimatorController == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stateHash;
|
||||||
|
float normalizedTime;
|
||||||
|
|
||||||
|
// This sends updates only if a layer change or transition is happening
|
||||||
for (int layer = 0; layer < m_Animator.layerCount; layer++)
|
for (int layer = 0; layer < m_Animator.layerCount; layer++)
|
||||||
{
|
{
|
||||||
int stateHash;
|
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
|
||||||
float normalizedTime;
|
var totalSpeed = st.speed * st.speedMultiplier;
|
||||||
|
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
|
||||||
|
|
||||||
|
// determine if we have reached the end of our state time, if so we can skip
|
||||||
|
if (st.normalizedTime >= adjustedNormalizedMaxTime)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
|
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -187,29 +492,120 @@ namespace Unity.Netcode.Components
|
|||||||
Weight = m_LayerWeights[layer]
|
Weight = m_LayerWeights[layer]
|
||||||
};
|
};
|
||||||
|
|
||||||
m_ParameterWriter.Seek(0);
|
if (!IsServer && IsOwner)
|
||||||
m_ParameterWriter.Truncate();
|
{
|
||||||
|
SendAnimStateServerRpc(animMsg);
|
||||||
WriteParameters(m_ParameterWriter);
|
}
|
||||||
animMsg.Parameters = m_ParameterWriter.ToArray();
|
else
|
||||||
|
{
|
||||||
SendAnimStateClientRpc(animMsg);
|
SendAnimStateClientRpc(animMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
|
private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, bool sendDirect = false)
|
||||||
|
{
|
||||||
|
m_ParameterWriter.Seek(0);
|
||||||
|
m_ParameterWriter.Truncate();
|
||||||
|
|
||||||
|
WriteParameters(m_ParameterWriter, sendDirect);
|
||||||
|
|
||||||
|
var parametersMessage = new ParametersUpdateMessage
|
||||||
|
{
|
||||||
|
Parameters = m_ParameterWriter.ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!IsServer)
|
||||||
|
{
|
||||||
|
SendParametersUpdateServerRpc(parametersMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (sendDirect)
|
||||||
|
{
|
||||||
|
SendParametersUpdateClientRpc(parametersMessage, clientRpcParams);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersMessage, clientRpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper function to get the cached value
|
||||||
|
/// </summary>
|
||||||
|
unsafe private T GetValue<T>(ref AnimatorParamCache animatorParamCache)
|
||||||
|
{
|
||||||
|
T currentValue;
|
||||||
|
fixed (void* value = animatorParamCache.Value)
|
||||||
|
{
|
||||||
|
currentValue = UnsafeUtility.ReadArrayElement<T>(value, 0);
|
||||||
|
}
|
||||||
|
return currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any of the Animator's parameters have changed
|
||||||
|
/// If so, it fills out m_ParametersToUpdate with the indices of the parameters
|
||||||
|
/// that have changed. Returns true if any parameters changed.
|
||||||
|
/// </summary>
|
||||||
|
unsafe private bool CheckParametersChanged()
|
||||||
|
{
|
||||||
|
m_ParametersToUpdate.Clear();
|
||||||
|
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
||||||
|
{
|
||||||
|
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
|
||||||
|
var hash = cacheValue.Hash;
|
||||||
|
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
||||||
|
{
|
||||||
|
var valueInt = m_Animator.GetInteger(hash);
|
||||||
|
var currentValue = GetValue<int>(ref cacheValue);
|
||||||
|
if (currentValue != valueInt)
|
||||||
|
{
|
||||||
|
m_ParametersToUpdate.Add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
|
||||||
|
{
|
||||||
|
var valueBool = m_Animator.GetBool(hash);
|
||||||
|
var currentValue = GetValue<bool>(ref cacheValue);
|
||||||
|
if (currentValue != valueBool)
|
||||||
|
{
|
||||||
|
m_ParametersToUpdate.Add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
||||||
|
{
|
||||||
|
var valueFloat = m_Animator.GetFloat(hash);
|
||||||
|
var currentValue = GetValue<float>(ref cacheValue);
|
||||||
|
if (currentValue != valueFloat)
|
||||||
|
{
|
||||||
|
m_ParametersToUpdate.Add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_ParametersToUpdate.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any of the Animator's states have changed
|
||||||
|
/// </summary>
|
||||||
|
private unsafe bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
|
||||||
{
|
{
|
||||||
bool shouldUpdate = false;
|
|
||||||
stateHash = 0;
|
stateHash = 0;
|
||||||
normalizedTime = 0;
|
normalizedTime = 0;
|
||||||
|
|
||||||
float layerWeightNow = m_Animator.GetLayerWeight(layer);
|
float layerWeightNow = m_Animator.GetLayerWeight(layer);
|
||||||
|
if (layerWeightNow != m_LayerWeights[layer])
|
||||||
if (!Mathf.Approximately(layerWeightNow, m_LayerWeights[layer]))
|
|
||||||
{
|
{
|
||||||
m_LayerWeights[layer] = layerWeightNow;
|
m_LayerWeights[layer] = layerWeightNow;
|
||||||
shouldUpdate = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Animator.IsInTransition(layer))
|
if (m_Animator.IsInTransition(layer))
|
||||||
{
|
{
|
||||||
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
|
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
|
||||||
@@ -218,7 +614,7 @@ namespace Unity.Netcode.Components
|
|||||||
// first time in this transition for this layer
|
// first time in this transition for this layer
|
||||||
m_TransitionHash[layer] = tt.fullPathHash;
|
m_TransitionHash[layer] = tt.fullPathHash;
|
||||||
m_AnimationHash[layer] = 0;
|
m_AnimationHash[layer] = 0;
|
||||||
shouldUpdate = true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -235,26 +631,26 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
m_TransitionHash[layer] = 0;
|
m_TransitionHash[layer] = 0;
|
||||||
m_AnimationHash[layer] = st.fullPathHash;
|
m_AnimationHash[layer] = st.fullPathHash;
|
||||||
shouldUpdate = true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
return shouldUpdate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* $AS TODO: Right now we are not checking for changed values this is because
|
/// <summary>
|
||||||
the read side of this function doesn't have similar logic which would cause
|
/// Writes all of the Animator's parameters
|
||||||
an overflow read because it doesn't know if the value is there or not. So
|
/// This uses the m_ParametersToUpdate list to write out only
|
||||||
there needs to be logic to track which indexes changed in order for there
|
/// the parameters that have changed
|
||||||
to be proper value change checking. Will revist in 1.1.0.
|
/// </summary>
|
||||||
*/
|
private unsafe void WriteParameters(FastBufferWriter writer, bool sendCacheState)
|
||||||
private unsafe void WriteParameters(FastBufferWriter writer)
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
// Write how many parameter entries we are going to write
|
||||||
|
BytePacker.WriteValuePacked(writer, (uint)m_ParametersToUpdate.Count);
|
||||||
|
foreach (var parameterIndex in m_ParametersToUpdate)
|
||||||
{
|
{
|
||||||
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
|
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), parameterIndex);
|
||||||
var hash = cacheValue.Hash;
|
var hash = cacheValue.Hash;
|
||||||
|
BytePacker.WriteValuePacked(writer, (uint)parameterIndex);
|
||||||
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
||||||
{
|
{
|
||||||
var valueInt = m_Animator.GetInteger(hash);
|
var valueInt = m_Animator.GetInteger(hash);
|
||||||
@@ -264,39 +660,46 @@ namespace Unity.Netcode.Components
|
|||||||
BytePacker.WriteValuePacked(writer, (uint)valueInt);
|
BytePacker.WriteValuePacked(writer, (uint)valueInt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
|
else // Note: Triggers are treated like boolean values
|
||||||
|
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
|
||||||
{
|
{
|
||||||
var valueBool = m_Animator.GetBool(hash);
|
var valueBool = m_Animator.GetBool(hash);
|
||||||
fixed (void* value = cacheValue.Value)
|
fixed (void* value = cacheValue.Value)
|
||||||
{
|
{
|
||||||
UnsafeUtility.WriteArrayElement(value, 0, valueBool);
|
UnsafeUtility.WriteArrayElement(value, 0, valueBool);
|
||||||
writer.WriteValueSafe(valueBool);
|
BytePacker.WriteValuePacked(writer, valueBool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
else
|
||||||
|
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
||||||
{
|
{
|
||||||
var valueFloat = m_Animator.GetFloat(hash);
|
var valueFloat = m_Animator.GetFloat(hash);
|
||||||
fixed (void* value = cacheValue.Value)
|
fixed (void* value = cacheValue.Value)
|
||||||
{
|
{
|
||||||
|
|
||||||
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
|
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
|
||||||
writer.WriteValueSafe(valueFloat);
|
BytePacker.WriteValuePacked(writer, valueFloat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all parameters that were updated and applies the values
|
||||||
|
/// </summary>
|
||||||
private unsafe void ReadParameters(FastBufferReader reader)
|
private unsafe void ReadParameters(FastBufferReader reader)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
ByteUnpacker.ReadValuePacked(reader, out uint totalParametersToRead);
|
||||||
{
|
var totalParametersRead = 0;
|
||||||
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
|
|
||||||
var hash = cacheValue.Hash;
|
|
||||||
|
|
||||||
|
while (totalParametersRead < totalParametersToRead)
|
||||||
|
{
|
||||||
|
ByteUnpacker.ReadValuePacked(reader, out uint parameterIndex);
|
||||||
|
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), (int)parameterIndex);
|
||||||
|
var hash = cacheValue.Hash;
|
||||||
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
||||||
{
|
{
|
||||||
ByteUnpacker.ReadValuePacked(reader, out int newValue);
|
ByteUnpacker.ReadValuePacked(reader, out uint newValue);
|
||||||
m_Animator.SetInteger(hash, newValue);
|
m_Animator.SetInteger(hash, (int)newValue);
|
||||||
fixed (void* value = cacheValue.Value)
|
fixed (void* value = cacheValue.Value)
|
||||||
{
|
{
|
||||||
UnsafeUtility.WriteArrayElement(value, 0, newValue);
|
UnsafeUtility.WriteArrayElement(value, 0, newValue);
|
||||||
@@ -304,7 +707,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
|
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
|
||||||
{
|
{
|
||||||
reader.ReadValueSafe(out bool newBoolValue);
|
ByteUnpacker.ReadValuePacked(reader, out bool newBoolValue);
|
||||||
m_Animator.SetBool(hash, newBoolValue);
|
m_Animator.SetBool(hash, newBoolValue);
|
||||||
fixed (void* value = cacheValue.Value)
|
fixed (void* value = cacheValue.Value)
|
||||||
{
|
{
|
||||||
@@ -313,42 +716,164 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
||||||
{
|
{
|
||||||
reader.ReadValueSafe(out float newFloatValue);
|
ByteUnpacker.ReadValuePacked(reader, out float newFloatValue);
|
||||||
m_Animator.SetFloat(hash, newFloatValue);
|
m_Animator.SetFloat(hash, newFloatValue);
|
||||||
fixed (void* value = cacheValue.Value)
|
fixed (void* value = cacheValue.Value)
|
||||||
{
|
{
|
||||||
UnsafeUtility.WriteArrayElement(value, 0, newFloatValue);
|
UnsafeUtility.WriteArrayElement(value, 0, newFloatValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
totalParametersRead++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internally-called RPC client receiving function to update some animation parameters on a client when
|
/// Applies the ParametersUpdateMessage state to the Animator
|
||||||
/// the server wants to update them
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="animSnapshot">the payload containing the parameters to apply</param>
|
internal unsafe void UpdateParameters(ParametersUpdateMessage parametersUpdate)
|
||||||
/// <param name="clientRpcParams">unused</param>
|
|
||||||
[ClientRpc]
|
|
||||||
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
|
|
||||||
{
|
{
|
||||||
if (animSnapshot.StateHash != 0)
|
if (parametersUpdate.Parameters != null && parametersUpdate.Parameters.Length != 0)
|
||||||
{
|
|
||||||
m_Animator.Play(animSnapshot.StateHash, animSnapshot.Layer, animSnapshot.NormalizedTime);
|
|
||||||
}
|
|
||||||
m_Animator.SetLayerWeight(animSnapshot.Layer, animSnapshot.Weight);
|
|
||||||
|
|
||||||
if (animSnapshot.Parameters != null && animSnapshot.Parameters.Length != 0)
|
|
||||||
{
|
{
|
||||||
// We use a fixed value here to avoid the copy of data from the byte buffer since we own the data
|
// 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)
|
fixed (byte* parameters = parametersUpdate.Parameters)
|
||||||
{
|
{
|
||||||
var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length);
|
var reader = new FastBufferReader(parameters, Allocator.None, parametersUpdate.Parameters.Length);
|
||||||
ReadParameters(reader);
|
ReadParameters(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the AnimationMessage state to the Animator
|
||||||
|
/// </summary>
|
||||||
|
private unsafe void UpdateAnimationState(AnimationMessage animationState)
|
||||||
|
{
|
||||||
|
if (animationState.StateHash != 0)
|
||||||
|
{
|
||||||
|
m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime);
|
||||||
|
}
|
||||||
|
m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-side animator parameter update request
|
||||||
|
/// The server sets its local parameters and then forwards the message to the remaining clients
|
||||||
|
/// </summary>
|
||||||
|
[ServerRpc]
|
||||||
|
private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parametersUpdate, ServerRpcParams serverRpcParams = default)
|
||||||
|
{
|
||||||
|
if (IsServerAuthoritative())
|
||||||
|
{
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateParameters(parametersUpdate);
|
||||||
|
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
|
||||||
|
{
|
||||||
|
m_ClientSendList.Clear();
|
||||||
|
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
|
||||||
|
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
|
||||||
|
m_ClientSendList.Remove(NetworkManager.ServerClientId);
|
||||||
|
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the client's animator's parameters
|
||||||
|
/// </summary>
|
||||||
|
[ClientRpc]
|
||||||
|
internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUpdate, ClientRpcParams clientRpcParams = default)
|
||||||
|
{
|
||||||
|
var isServerAuthoritative = IsServerAuthoritative();
|
||||||
|
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
|
||||||
|
{
|
||||||
|
m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-side animation state update request
|
||||||
|
/// The server sets its local state and then forwards the message to the remaining clients
|
||||||
|
/// </summary>
|
||||||
|
[ServerRpc]
|
||||||
|
private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, ServerRpcParams serverRpcParams = default)
|
||||||
|
{
|
||||||
|
if (IsServerAuthoritative())
|
||||||
|
{
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateAnimationState(animSnapshot);
|
||||||
|
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
|
||||||
|
{
|
||||||
|
m_ClientSendList.Clear();
|
||||||
|
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
|
||||||
|
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
|
||||||
|
m_ClientSendList.Remove(NetworkManager.ServerClientId);
|
||||||
|
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot, m_ClientRpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internally-called RPC client receiving function to update some animation state on a client
|
||||||
|
/// </summary>
|
||||||
|
[ClientRpc]
|
||||||
|
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
|
||||||
|
{
|
||||||
|
var isServerAuthoritative = IsServerAuthoritative();
|
||||||
|
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
|
||||||
|
{
|
||||||
|
UpdateAnimationState(animSnapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-side trigger state update request
|
||||||
|
/// The server sets its local state and then forwards the message to the remaining clients
|
||||||
|
/// </summary>
|
||||||
|
[ServerRpc]
|
||||||
|
private void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
|
||||||
|
{
|
||||||
|
if (IsServerAuthoritative())
|
||||||
|
{
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// trigger the animation locally on the server...
|
||||||
|
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
|
||||||
|
|
||||||
|
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0)
|
||||||
|
{
|
||||||
|
m_ClientSendList.Clear();
|
||||||
|
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
|
||||||
|
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
|
||||||
|
m_ClientSendList.Remove(NetworkManager.ServerClientId);
|
||||||
|
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
||||||
|
m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage, m_ClientRpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internally-called RPC client receiving function to update a trigger when the server wants to forward
|
/// Internally-called RPC client receiving function to update a trigger when the server wants to forward
|
||||||
/// a trigger for a client to play / reset
|
/// a trigger for a client to play / reset
|
||||||
@@ -356,24 +881,17 @@ namespace Unity.Netcode.Components
|
|||||||
/// <param name="animSnapshot">the payload containing the trigger data to apply</param>
|
/// <param name="animSnapshot">the payload containing the trigger data to apply</param>
|
||||||
/// <param name="clientRpcParams">unused</param>
|
/// <param name="clientRpcParams">unused</param>
|
||||||
[ClientRpc]
|
[ClientRpc]
|
||||||
private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default)
|
internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
|
||||||
{
|
{
|
||||||
if (animSnapshot.Reset)
|
var isServerAuthoritative = IsServerAuthoritative();
|
||||||
|
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
|
||||||
{
|
{
|
||||||
m_Animator.ResetTrigger(animSnapshot.Hash);
|
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_Animator.SetTrigger(animSnapshot.Hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the trigger for the associated animation
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="triggerName">The string name of the trigger to activate</param>
|
/// <param name="triggerName">The string name of the trigger to activate</param>
|
||||||
public void SetTrigger(string triggerName)
|
public void SetTrigger(string triggerName)
|
||||||
@@ -383,31 +901,23 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
/// <inheritdoc cref="SetTrigger(string)" />
|
/// <inheritdoc cref="SetTrigger(string)" />
|
||||||
/// <param name="hash">The hash for the trigger to activate</param>
|
/// <param name="hash">The hash for the trigger to activate</param>
|
||||||
/// <param name="reset">If true, resets the trigger</param>
|
/// <param name="setTrigger">sets (true) or resets (false) the trigger. The default is to set it (true).</param>
|
||||||
public void SetTrigger(int hash, bool reset = false)
|
public void SetTrigger(int hash, bool setTrigger = true)
|
||||||
{
|
{
|
||||||
var animMsg = new AnimationTriggerMessage();
|
var isServerAuthoritative = IsServerAuthoritative();
|
||||||
animMsg.Hash = hash;
|
if (IsOwner && !isServerAuthoritative || IsServer && isServerAuthoritative)
|
||||||
animMsg.Reset = reset;
|
|
||||||
|
|
||||||
if (IsServer)
|
|
||||||
{
|
{
|
||||||
// trigger the animation locally on the server...
|
var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger };
|
||||||
if (reset)
|
if (IsServer)
|
||||||
{
|
{
|
||||||
m_Animator.ResetTrigger(hash);
|
SendAnimTriggerClientRpc(animTriggerMessage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_Animator.SetTrigger(hash);
|
SendAnimTriggerServerRpc(animTriggerMessage);
|
||||||
}
|
}
|
||||||
|
// trigger the animation locally on the server...
|
||||||
// ...then tell all the clients to do the same
|
m_Animator.SetBool(hash, setTrigger);
|
||||||
SendAnimTriggerClientRpc(animMsg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogWarning("Trying to call NetworkAnimator.SetTrigger on a client...ignoring");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +934,7 @@ namespace Unity.Netcode.Components
|
|||||||
/// <param name="hash">The hash for the trigger to activate</param>
|
/// <param name="hash">The hash for the trigger to activate</param>
|
||||||
public void ResetTrigger(int hash)
|
public void ResetTrigger(int hash)
|
||||||
{
|
{
|
||||||
SetTrigger(hash, true);
|
SetTrigger(hash, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,77 +5,97 @@ namespace Unity.Netcode.Components
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
|
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
|
||||||
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
|
/// mode of the <see cref="Rigidbody"/> and disabling it on all peers but the authoritative one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RequireComponent(typeof(Rigidbody))]
|
[RequireComponent(typeof(Rigidbody))]
|
||||||
[RequireComponent(typeof(NetworkTransform))]
|
[RequireComponent(typeof(NetworkTransform))]
|
||||||
public class NetworkRigidbody : NetworkBehaviour
|
public class NetworkRigidbody : NetworkBehaviour
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if we are server (true) or owner (false) authoritative
|
||||||
|
/// </summary>
|
||||||
|
private bool m_IsServerAuthoritative;
|
||||||
|
|
||||||
private Rigidbody m_Rigidbody;
|
private Rigidbody m_Rigidbody;
|
||||||
private NetworkTransform m_NetworkTransform;
|
private NetworkTransform m_NetworkTransform;
|
||||||
|
|
||||||
private bool m_OriginalKinematic;
|
|
||||||
private RigidbodyInterpolation m_OriginalInterpolation;
|
private RigidbodyInterpolation m_OriginalInterpolation;
|
||||||
|
|
||||||
// Used to cache the authority state of this rigidbody during the last frame
|
// Used to cache the authority state of this Rigidbody during the last frame
|
||||||
private bool m_IsAuthority;
|
private bool m_IsAuthority;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody"/> on this peer currently holds authority.
|
|
||||||
/// </summary>
|
|
||||||
private bool HasAuthority => m_NetworkTransform.CanCommitToTransform;
|
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
m_Rigidbody = GetComponent<Rigidbody>();
|
|
||||||
m_NetworkTransform = GetComponent<NetworkTransform>();
|
m_NetworkTransform = GetComponent<NetworkTransform>();
|
||||||
|
m_IsServerAuthoritative = m_NetworkTransform.IsServerAuthoritative();
|
||||||
|
|
||||||
|
m_Rigidbody = GetComponent<Rigidbody>();
|
||||||
|
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||||
|
|
||||||
|
// Set interpolation to none if NetworkTransform is handling interpolation, otherwise it sets it to the original value
|
||||||
|
m_Rigidbody.interpolation = m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation;
|
||||||
|
|
||||||
|
// Turn off physics for the rigid body until spawned, otherwise
|
||||||
|
// clients can run fixed update before the first full
|
||||||
|
// NetworkTransform update
|
||||||
|
m_Rigidbody.isKinematic = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FixedUpdate()
|
/// <summary>
|
||||||
|
/// For owner authoritative (i.e. ClientNetworkTransform)
|
||||||
|
/// we adjust our authority when we gain ownership
|
||||||
|
/// </summary>
|
||||||
|
public override void OnGainedOwnership()
|
||||||
{
|
{
|
||||||
if (NetworkManager.IsListening)
|
UpdateOwnershipAuthority();
|
||||||
{
|
|
||||||
if (HasAuthority != m_IsAuthority)
|
|
||||||
{
|
|
||||||
m_IsAuthority = HasAuthority;
|
|
||||||
UpdateRigidbodyKinematicMode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
|
/// <summary>
|
||||||
private void UpdateRigidbodyKinematicMode()
|
/// For owner authoritative(i.e. ClientNetworkTransform)
|
||||||
|
/// we adjust our authority when we have lost ownership
|
||||||
|
/// </summary>
|
||||||
|
public override void OnLostOwnership()
|
||||||
{
|
{
|
||||||
if (m_IsAuthority == false)
|
UpdateOwnershipAuthority();
|
||||||
{
|
}
|
||||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
|
||||||
m_Rigidbody.isKinematic = true;
|
|
||||||
|
|
||||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
/// <summary>
|
||||||
// Set interpolation to none, the NetworkTransform component interpolates the position of the object.
|
/// Sets the authority differently depending upon
|
||||||
m_Rigidbody.interpolation = RigidbodyInterpolation.None;
|
/// whether it is server or owner authoritative
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateOwnershipAuthority()
|
||||||
|
{
|
||||||
|
if (m_IsServerAuthoritative)
|
||||||
|
{
|
||||||
|
m_IsAuthority = NetworkManager.IsServer;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
|
m_IsAuthority = IsOwner;
|
||||||
m_Rigidbody.isKinematic = m_OriginalKinematic;
|
|
||||||
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If you have authority then you are not kinematic
|
||||||
|
m_Rigidbody.isKinematic = !m_IsAuthority;
|
||||||
|
|
||||||
|
// Set interpolation of the Rigidbody based on authority
|
||||||
|
// With authority: let local transform handle interpolation
|
||||||
|
// Without authority: let the NetworkTransform handle interpolation
|
||||||
|
m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : RigidbodyInterpolation.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnNetworkSpawn()
|
public override void OnNetworkSpawn()
|
||||||
{
|
{
|
||||||
m_IsAuthority = HasAuthority;
|
UpdateOwnershipAuthority();
|
||||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
|
||||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
|
||||||
UpdateRigidbodyKinematicMode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnNetworkDespawn()
|
public override void OnNetworkDespawn()
|
||||||
{
|
{
|
||||||
UpdateRigidbodyKinematicMode();
|
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
||||||
|
// Turn off physics for the rigid body until spawned, otherwise
|
||||||
|
// non-owners can run fixed updates before the first full
|
||||||
|
// NetworkTransform update and physics will be applied (i.e. gravity, etc)
|
||||||
|
m_Rigidbody.isKinematic = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Random = UnityEngine.Random;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.Components
|
namespace Unity.Netcode.Components
|
||||||
{
|
{
|
||||||
@@ -15,9 +14,10 @@ namespace Unity.Netcode.Components
|
|||||||
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
|
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
|
||||||
public class NetworkTransform : NetworkBehaviour
|
public class NetworkTransform : NetworkBehaviour
|
||||||
{
|
{
|
||||||
public const float PositionThresholdDefault = .001f;
|
public const float PositionThresholdDefault = 0.001f;
|
||||||
public const float RotAngleThresholdDefault = .01f;
|
public const float RotAngleThresholdDefault = 0.01f;
|
||||||
public const float ScaleThresholdDefault = .01f;
|
public const float ScaleThresholdDefault = 0.01f;
|
||||||
|
|
||||||
public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale);
|
public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale);
|
||||||
public OnClientRequestChangeDelegate OnClientRequestChange;
|
public OnClientRequestChangeDelegate OnClientRequestChange;
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ namespace Unity.Netcode.Components
|
|||||||
// 11-15: <unused>
|
// 11-15: <unused>
|
||||||
private ushort m_Bitset;
|
private ushort m_Bitset;
|
||||||
|
|
||||||
public bool InLocalSpace
|
internal bool InLocalSpace
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_InLocalSpaceBit)) != 0;
|
get => (m_Bitset & (1 << k_InLocalSpaceBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -49,7 +49,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Position
|
// Position
|
||||||
public bool HasPositionX
|
internal bool HasPositionX
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_PositionXBit)) != 0;
|
get => (m_Bitset & (1 << k_PositionXBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -59,7 +59,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasPositionY
|
internal bool HasPositionY
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_PositionYBit)) != 0;
|
get => (m_Bitset & (1 << k_PositionYBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -69,7 +69,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasPositionZ
|
internal bool HasPositionZ
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_PositionZBit)) != 0;
|
get => (m_Bitset & (1 << k_PositionZBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -80,7 +80,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RotAngles
|
// RotAngles
|
||||||
public bool HasRotAngleX
|
internal bool HasRotAngleX
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_RotAngleXBit)) != 0;
|
get => (m_Bitset & (1 << k_RotAngleXBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -90,7 +90,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasRotAngleY
|
internal bool HasRotAngleY
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_RotAngleYBit)) != 0;
|
get => (m_Bitset & (1 << k_RotAngleYBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -100,7 +100,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasRotAngleZ
|
internal bool HasRotAngleZ
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_RotAngleZBit)) != 0;
|
get => (m_Bitset & (1 << k_RotAngleZBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -111,7 +111,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scale
|
// Scale
|
||||||
public bool HasScaleX
|
internal bool HasScaleX
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_ScaleXBit)) != 0;
|
get => (m_Bitset & (1 << k_ScaleXBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -121,7 +121,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasScaleY
|
internal bool HasScaleY
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_ScaleYBit)) != 0;
|
get => (m_Bitset & (1 << k_ScaleYBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -131,7 +131,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasScaleZ
|
internal bool HasScaleZ
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_ScaleZBit)) != 0;
|
get => (m_Bitset & (1 << k_ScaleZBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -141,7 +141,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsTeleportingNextFrame
|
internal bool IsTeleportingNextFrame
|
||||||
{
|
{
|
||||||
get => (m_Bitset & (1 << k_TeleportingBit)) != 0;
|
get => (m_Bitset & (1 << k_TeleportingBit)) != 0;
|
||||||
set
|
set
|
||||||
@@ -151,12 +151,12 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float PositionX, PositionY, PositionZ;
|
internal float PositionX, PositionY, PositionZ;
|
||||||
public float RotAngleX, RotAngleY, RotAngleZ;
|
internal float RotAngleX, RotAngleY, RotAngleZ;
|
||||||
public float ScaleX, ScaleY, ScaleZ;
|
internal float ScaleX, ScaleY, ScaleZ;
|
||||||
public double SentTime;
|
internal double SentTime;
|
||||||
|
|
||||||
public Vector3 Position
|
internal Vector3 Position
|
||||||
{
|
{
|
||||||
get { return new Vector3(PositionX, PositionY, PositionZ); }
|
get { return new Vector3(PositionX, PositionY, PositionZ); }
|
||||||
set
|
set
|
||||||
@@ -167,7 +167,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3 Rotation
|
internal Vector3 Rotation
|
||||||
{
|
{
|
||||||
get { return new Vector3(RotAngleX, RotAngleY, RotAngleZ); }
|
get { return new Vector3(RotAngleX, RotAngleY, RotAngleZ); }
|
||||||
set
|
set
|
||||||
@@ -178,7 +178,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3 Scale
|
internal Vector3 Scale
|
||||||
{
|
{
|
||||||
get { return new Vector3(ScaleX, ScaleY, ScaleZ); }
|
get { return new Vector3(ScaleX, ScaleY, ScaleZ); }
|
||||||
set
|
set
|
||||||
@@ -249,7 +249,10 @@ namespace Unity.Netcode.Components
|
|||||||
public bool SyncScaleX = true, SyncScaleY = true, SyncScaleZ = true;
|
public bool SyncScaleX = true, SyncScaleY = true, SyncScaleZ = true;
|
||||||
|
|
||||||
public float PositionThreshold = PositionThresholdDefault;
|
public float PositionThreshold = PositionThresholdDefault;
|
||||||
|
|
||||||
|
[Range(0.001f, 360.0f)]
|
||||||
public float RotAngleThreshold = RotAngleThresholdDefault;
|
public float RotAngleThreshold = RotAngleThresholdDefault;
|
||||||
|
|
||||||
public float ScaleThreshold = ScaleThresholdDefault;
|
public float ScaleThreshold = ScaleThresholdDefault;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -280,8 +283,6 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
private NetworkTransformState m_LocalAuthoritativeNetworkState;
|
private NetworkTransformState m_LocalAuthoritativeNetworkState;
|
||||||
|
|
||||||
private NetworkTransformState m_PrevNetworkState;
|
|
||||||
|
|
||||||
private const int k_DebugDrawLineTime = 10;
|
private const int k_DebugDrawLineTime = 10;
|
||||||
|
|
||||||
private bool m_HasSentLastValue = false; // used to send one last value, so clients can make the difference between lost replication data (clients extrapolate) and no more data to send.
|
private bool m_HasSentLastValue = false; // used to send one last value, so clients can make the difference between lost replication data (clients extrapolate) and no more data to send.
|
||||||
@@ -390,6 +391,16 @@ namespace Unity.Netcode.Components
|
|||||||
m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime);
|
m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed isDirty information returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transform">transform to apply</param>
|
||||||
|
/// <returns>bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty</returns>
|
||||||
|
internal (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyLocalNetworkState(Transform transform)
|
||||||
|
{
|
||||||
|
return ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, m_CachedNetworkManager.LocalTime.Time, transform);
|
||||||
|
}
|
||||||
|
|
||||||
// updates `NetworkState` properties if they need to and returns a `bool` indicating whether or not there was any changes made
|
// updates `NetworkState` properties if they need to and returns a `bool` indicating whether or not there was any changes made
|
||||||
// returned boolean would be useful to change encapsulating `NetworkVariable<NetworkState>`'s dirty state, e.g. ReplNetworkState.SetDirty(isDirty);
|
// returned boolean would be useful to change encapsulating `NetworkVariable<NetworkState>`'s dirty state, e.g. ReplNetworkState.SetDirty(isDirty);
|
||||||
internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
|
internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
|
||||||
@@ -450,7 +461,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SyncRotAngleX &&
|
if (SyncRotAngleX &&
|
||||||
Mathf.Abs(networkState.RotAngleX - rotAngles.x) > RotAngleThreshold)
|
Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) > RotAngleThreshold)
|
||||||
{
|
{
|
||||||
networkState.RotAngleX = rotAngles.x;
|
networkState.RotAngleX = rotAngles.x;
|
||||||
networkState.HasRotAngleX = true;
|
networkState.HasRotAngleX = true;
|
||||||
@@ -458,7 +469,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SyncRotAngleY &&
|
if (SyncRotAngleY &&
|
||||||
Mathf.Abs(networkState.RotAngleY - rotAngles.y) > RotAngleThreshold)
|
Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) > RotAngleThreshold)
|
||||||
{
|
{
|
||||||
networkState.RotAngleY = rotAngles.y;
|
networkState.RotAngleY = rotAngles.y;
|
||||||
networkState.HasRotAngleY = true;
|
networkState.HasRotAngleY = true;
|
||||||
@@ -466,7 +477,7 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SyncRotAngleZ &&
|
if (SyncRotAngleZ &&
|
||||||
Mathf.Abs(networkState.RotAngleZ - rotAngles.z) > RotAngleThreshold)
|
Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) > RotAngleThreshold)
|
||||||
{
|
{
|
||||||
networkState.RotAngleZ = rotAngles.z;
|
networkState.RotAngleZ = rotAngles.z;
|
||||||
networkState.HasRotAngleZ = true;
|
networkState.HasRotAngleZ = true;
|
||||||
@@ -509,8 +520,6 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState networkState, Transform transformToUpdate)
|
private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState networkState, Transform transformToUpdate)
|
||||||
{
|
{
|
||||||
m_PrevNetworkState = networkState;
|
|
||||||
|
|
||||||
var interpolatedPosition = InLocalSpace ? transformToUpdate.localPosition : transformToUpdate.position;
|
var interpolatedPosition = InLocalSpace ? transformToUpdate.localPosition : transformToUpdate.position;
|
||||||
|
|
||||||
// todo: we should store network state w/ quats vs. euler angles
|
// todo: we should store network state w/ quats vs. euler angles
|
||||||
@@ -587,8 +596,6 @@ namespace Unity.Netcode.Components
|
|||||||
{
|
{
|
||||||
transformToUpdate.position = interpolatedPosition;
|
transformToUpdate.position = interpolatedPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_PrevNetworkState.Position = interpolatedPosition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RotAngles Apply
|
// RotAngles Apply
|
||||||
@@ -602,15 +609,12 @@ namespace Unity.Netcode.Components
|
|||||||
{
|
{
|
||||||
transformToUpdate.rotation = Quaternion.Euler(interpolatedRotAngles);
|
transformToUpdate.rotation = Quaternion.Euler(interpolatedRotAngles);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_PrevNetworkState.Rotation = interpolatedRotAngles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale Apply
|
// Scale Apply
|
||||||
if (SyncScaleX || SyncScaleY || SyncScaleZ)
|
if (SyncScaleX || SyncScaleY || SyncScaleZ)
|
||||||
{
|
{
|
||||||
transformToUpdate.localScale = interpolatedScale;
|
transformToUpdate.localScale = interpolatedScale;
|
||||||
m_PrevNetworkState.Scale = interpolatedScale;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,8 +704,6 @@ namespace Unity.Netcode.Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.DrawLine(newState.Position, newState.Position + Vector3.up + Vector3.left, Color.green, 10, false);
|
|
||||||
|
|
||||||
if (Interpolate)
|
if (Interpolate)
|
||||||
{
|
{
|
||||||
AddInterpolatedState(newState, (newState.InLocalSpace != m_LastInterpolateLocal));
|
AddInterpolatedState(newState, (newState.InLocalSpace != m_LastInterpolateLocal));
|
||||||
@@ -711,10 +713,20 @@ namespace Unity.Netcode.Components
|
|||||||
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
|
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
|
||||||
{
|
{
|
||||||
var pos = new Vector3(newState.PositionX, newState.PositionY, newState.PositionZ);
|
var pos = new Vector3(newState.PositionX, newState.PositionY, newState.PositionZ);
|
||||||
Debug.DrawLine(pos, pos + Vector3.up + Vector3.left * Random.Range(0.5f, 2f), Color.green, k_DebugDrawLineTime, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetMaxInterpolationBound(float maxInterpolationBound)
|
||||||
|
{
|
||||||
|
m_PositionXInterpolator.MaxInterpolationBound = maxInterpolationBound;
|
||||||
|
m_PositionYInterpolator.MaxInterpolationBound = maxInterpolationBound;
|
||||||
|
m_PositionZInterpolator.MaxInterpolationBound = maxInterpolationBound;
|
||||||
|
m_RotationInterpolator.MaxInterpolationBound = maxInterpolationBound;
|
||||||
|
m_ScaleXInterpolator.MaxInterpolationBound = maxInterpolationBound;
|
||||||
|
m_ScaleYInterpolator.MaxInterpolationBound = maxInterpolationBound;
|
||||||
|
m_ScaleZInterpolator.MaxInterpolationBound = maxInterpolationBound;
|
||||||
|
}
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
// we only want to create our interpolators during Awake so that, when pooled, we do not create tons
|
// we only want to create our interpolators during Awake so that, when pooled, we do not create tons
|
||||||
@@ -790,8 +802,6 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region state set
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Directly sets a state on the authoritative transform.
|
/// Directly sets a state on the authoritative transform.
|
||||||
/// This will override any changes made previously to the transform
|
/// This will override any changes made previously to the transform
|
||||||
@@ -851,7 +861,6 @@ namespace Unity.Netcode.Components
|
|||||||
m_Transform.localScale = scale;
|
m_Transform.localScale = scale;
|
||||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;
|
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
// todo this is currently in update, to be able to catch any transform changes. A FixedUpdate mode could be added to be less intense, but it'd be
|
// todo this is currently in update, to be able to catch any transform changes. A FixedUpdate mode could be added to be less intense, but it'd be
|
||||||
// conditional to users only making transform update changes in FixedUpdate.
|
// conditional to users only making transform update changes in FixedUpdate.
|
||||||
@@ -879,8 +888,6 @@ namespace Unity.Netcode.Components
|
|||||||
{
|
{
|
||||||
TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time);
|
TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_PrevNetworkState = m_LocalAuthoritativeNetworkState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply interpolated value
|
// apply interpolated value
|
||||||
@@ -904,36 +911,10 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
if (!CanCommitToTransform)
|
if (!CanCommitToTransform)
|
||||||
{
|
{
|
||||||
#if NGO_TRANSFORM_DEBUG
|
|
||||||
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
|
|
||||||
{
|
|
||||||
// TODO: This should be a component gizmo - not some debug draw based on log level
|
|
||||||
var interpolatedPosition = new Vector3(m_PositionXInterpolator.GetInterpolatedValue(), m_PositionYInterpolator.GetInterpolatedValue(), m_PositionZInterpolator.GetInterpolatedValue());
|
|
||||||
Debug.DrawLine(interpolatedPosition, interpolatedPosition + Vector3.up, Color.magenta, k_DebugDrawLineTime, false);
|
|
||||||
|
|
||||||
// try to update previously consumed NetworkState
|
|
||||||
// if we have any changes, that means made some updates locally
|
|
||||||
// we apply the latest ReplNetworkState again to revert our changes
|
|
||||||
var oldStateDirtyInfo = ApplyTransformToNetworkStateWithInfo(ref m_PrevNetworkState, 0, m_Transform);
|
|
||||||
|
|
||||||
// there are several bugs in this code, as we the message is dumped out under odd circumstances
|
|
||||||
// For Matt, it would trigger when an object's rotation was perturbed by colliding with another
|
|
||||||
// object vs. explicitly rotating it
|
|
||||||
if (oldStateDirtyInfo.isPositionDirty || oldStateDirtyInfo.isScaleDirty || (oldStateDirtyInfo.isRotationDirty && SyncRotAngleX && SyncRotAngleY && SyncRotAngleZ))
|
|
||||||
{
|
|
||||||
// ignoring rotation dirty since quaternions will mess with euler angles, making this impossible to determine if the change to a single axis comes
|
|
||||||
// from an unauthorized transform change or euler to quaternion conversion artifacts.
|
|
||||||
var dirtyField = oldStateDirtyInfo.isPositionDirty ? "position" : oldStateDirtyInfo.isRotationDirty ? "rotation" : "scale";
|
|
||||||
Debug.LogWarning($"A local change to {dirtyField} without authority detected, reverting back to latest interpolated network state!", this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Apply updated interpolated value
|
// Apply updated interpolated value
|
||||||
ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform);
|
ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -960,5 +941,21 @@ namespace Unity.Netcode.Components
|
|||||||
TryCommitValuesToServer(newPosition, newRotationEuler, newScale, m_CachedNetworkManager.LocalTime.Time);
|
TryCommitValuesToServer(newPosition, newRotationEuler, newScale, m_CachedNetworkManager.LocalTime.Time);
|
||||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override this method and return false to switch to owner authoritative mode
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool OnIsServerAuthoritative()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by <see cref="NetworkRigidbody"/> to determines if this is server or owner authoritative.
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsServerAuthoritative()
|
||||||
|
{
|
||||||
|
return OnIsServerAuthoritative();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# About Netcode for GameObjects
|
|
||||||
|
|
||||||
Unity Netcode for GameObjects is a high-level networking library built to abstract networking. This allows developers to focus on the game rather than low level protocols and networking frameworks.
|
|
||||||
|
|
||||||
## Guides
|
|
||||||
|
|
||||||
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
|
|
||||||
|
|
||||||
* [Documentation](https://docs-multiplayer.unity3d.com/docs/getting-started/about-mlapi)
|
|
||||||
* [Installation](https://docs-multiplayer.unity3d.com/docs/migration/install)
|
|
||||||
* [First Steps](https://docs-multiplayer.unity3d.com/docs/tutorials/helloworld/helloworldintro)
|
|
||||||
* [API Reference](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
|
||||||
|
|
||||||
# Technical details
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
This version of Netcode for GameObjects is compatible with the following Unity versions and platforms:
|
|
||||||
|
|
||||||
* 2020.3 and later
|
|
||||||
* Windows, Mac, Linux platforms
|
|
||||||
|
|
||||||
## Document revision history
|
|
||||||
|
|
||||||
|Date|Reason|
|
|
||||||
|---|---|
|
|
||||||
|March 10, 2021|Document created. Matches package version 0.1.0|
|
|
||||||
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.|
|
|
||||||
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|
|
||||||
|August 5, 2021|Update product/package name|
|
|
||||||
|September 9,2021|Updated the links and name of the file.|
|
|
||||||
35
Documentation~/index.md
Normal file
35
Documentation~/index.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# About Netcode for GameObjects
|
||||||
|
|
||||||
|
Netcode for GameObjects is a Unity package that provides networking capabilities to GameObject & MonoBehaviour workflows.
|
||||||
|
|
||||||
|
## Guides
|
||||||
|
|
||||||
|
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
|
||||||
|
|
||||||
|
- [Documentation](https://docs-multiplayer.unity3d.com/netcode/current/about)
|
||||||
|
- [Installation](https://docs-multiplayer.unity3d.com/netcode/current/migration/install)
|
||||||
|
- [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro)
|
||||||
|
- [API Reference](https://docs-multiplayer.unity3d.com/netcode/current/api/introduction)
|
||||||
|
|
||||||
|
# Technical details
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Netcode for GameObjects targets the following Unity versions:
|
||||||
|
- Unity 2020.3, 2021.1, 2021.2 and 2021.3
|
||||||
|
|
||||||
|
On the following runtime platforms:
|
||||||
|
- Windows, MacOS, and Linux
|
||||||
|
- iOS and Android
|
||||||
|
- Most closed platforms, such as consoles. Contact us for more information about specific closed platforms.
|
||||||
|
|
||||||
|
## Document revision history
|
||||||
|
|
||||||
|
|Date|Reason|
|
||||||
|
|---|---|
|
||||||
|
|March 10, 2021|Document created. Matches package version 0.1.0|
|
||||||
|
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.|
|
||||||
|
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|
||||||
|
|August 5, 2021|Update product/package name|
|
||||||
|
|September 9,2021|Updated the links and name of the file.|
|
||||||
|
|April 20, 2022|Updated links|
|
||||||
@@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using Mono.Cecil.Rocks;
|
using Mono.Cecil.Rocks;
|
||||||
|
using Unity.Collections;
|
||||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -27,6 +28,8 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
|
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
|
||||||
public static readonly string ServerRpcReceiveParams_FullName = typeof(ServerRpcReceiveParams).FullName;
|
public static readonly string ServerRpcReceiveParams_FullName = typeof(ServerRpcReceiveParams).FullName;
|
||||||
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName;
|
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName;
|
||||||
|
public static readonly string INetworkSerializeByMemcpy_FullName = typeof(INetworkSerializeByMemcpy).FullName;
|
||||||
|
public static readonly string IUTF8Bytes_FullName = typeof(IUTF8Bytes).FullName;
|
||||||
public static readonly string UnityColor_FullName = typeof(Color).FullName;
|
public static readonly string UnityColor_FullName = typeof(Color).FullName;
|
||||||
public static readonly string UnityColor32_FullName = typeof(Color32).FullName;
|
public static readonly string UnityColor32_FullName = typeof(Color32).FullName;
|
||||||
public static readonly string UnityVector2_FullName = typeof(Vector2).FullName;
|
public static readonly string UnityVector2_FullName = typeof(Vector2).FullName;
|
||||||
@@ -77,6 +80,35 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string FullNameWithGenericParameters(this TypeReference typeReference, GenericParameter[] contextGenericParameters, TypeReference[] contextGenericParameterTypes)
|
||||||
|
{
|
||||||
|
var name = typeReference.FullName;
|
||||||
|
if (typeReference.HasGenericParameters)
|
||||||
|
{
|
||||||
|
name += "<";
|
||||||
|
for (var i = 0; i < typeReference.Resolve().GenericParameters.Count; ++i)
|
||||||
|
{
|
||||||
|
if (i != 0)
|
||||||
|
{
|
||||||
|
name += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < contextGenericParameters.Length; ++j)
|
||||||
|
{
|
||||||
|
if (typeReference.GenericParameters[i].FullName == contextGenericParameters[i].FullName)
|
||||||
|
{
|
||||||
|
name += contextGenericParameterTypes[i].FullName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name += ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
|
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
|
||||||
{
|
{
|
||||||
if (typeReference.IsArray)
|
if (typeReference.IsArray)
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using Mono.Cecil.Rocks;
|
|
||||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||||
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.Editor.CodeGen
|
namespace Unity.Netcode.Editor.CodeGen
|
||||||
{
|
{
|
||||||
@@ -24,6 +21,30 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
|
|
||||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||||
|
|
||||||
|
private TypeReference ResolveGenericType(TypeReference type, List<TypeReference> typeStack)
|
||||||
|
{
|
||||||
|
var genericName = type.Name;
|
||||||
|
var lastType = (GenericInstanceType)typeStack[typeStack.Count - 1];
|
||||||
|
var resolvedType = lastType.Resolve();
|
||||||
|
typeStack.RemoveAt(typeStack.Count - 1);
|
||||||
|
for (var i = 0; i < resolvedType.GenericParameters.Count; ++i)
|
||||||
|
{
|
||||||
|
var parameter = resolvedType.GenericParameters[i];
|
||||||
|
if (parameter.Name == genericName)
|
||||||
|
{
|
||||||
|
var underlyingType = lastType.GenericArguments[i];
|
||||||
|
if (underlyingType.Resolve() == null)
|
||||||
|
{
|
||||||
|
return ResolveGenericType(underlyingType, typeStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
return underlyingType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||||
{
|
{
|
||||||
if (!WillProcess(compiledAssembly))
|
if (!WillProcess(compiledAssembly))
|
||||||
@@ -31,7 +52,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
m_Diagnostics.Clear();
|
m_Diagnostics.Clear();
|
||||||
|
|
||||||
// read
|
// read
|
||||||
@@ -48,22 +68,26 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ImportReferences(mainModule))
|
var structTypes = mainModule.GetTypes()
|
||||||
{
|
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
|
||||||
var types = mainModule.GetTypes()
|
.ToList();
|
||||||
.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);
|
foreach (var type in structTypes)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
|
// We'll avoid some confusion by ensuring users only choose one of the two
|
||||||
|
// serialization schemes - by method OR by memcpy, not both. We'll also do a cursory
|
||||||
|
// check that INetworkSerializeByMemcpy types are unmanaged.
|
||||||
|
if (type.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
|
||||||
|
{
|
||||||
|
if (type.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
|
||||||
|
{
|
||||||
|
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other.");
|
||||||
|
}
|
||||||
|
if (!type.IsValueType)
|
||||||
|
{
|
||||||
|
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -93,75 +117,5 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
|
|
||||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -509,6 +509,12 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (methodDefinition.HasGenericParameters)
|
||||||
|
{
|
||||||
|
m_Diagnostics.AddError(methodDefinition, "RPC method must not be generic!");
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (methodDefinition.ReturnType != methodDefinition.Module.TypeSystem.Void)
|
if (methodDefinition.ReturnType != methodDefinition.Module.TypeSystem.Void)
|
||||||
{
|
{
|
||||||
m_Diagnostics.AddError(methodDefinition, "RPC method must return `void`!");
|
m_Diagnostics.AddError(methodDefinition, "RPC method must return `void`!");
|
||||||
@@ -533,6 +539,10 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
{
|
{
|
||||||
rpcAttribute = customAttribute;
|
rpcAttribute = customAttribute;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,11 +585,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
var checkType = paramType.Resolve();
|
var checkType = paramType.Resolve();
|
||||||
if (paramType.IsArray)
|
if (paramType.IsArray)
|
||||||
{
|
{
|
||||||
checkType = paramType.GetElementType().Resolve();
|
checkType = ((ArrayType)paramType).ElementType.Resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((parameters[0].ParameterType.Resolve() == checkType ||
|
if ((parameters[0].ParameterType.Resolve() == checkType ||
|
||||||
(parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
|
(parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
|
||||||
|
{
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters[0].ParameterType == paramType ||
|
||||||
|
(parameters[0].ParameterType == paramType.MakeByReferenceType() && parameters[0].IsIn))
|
||||||
{
|
{
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
@@ -591,10 +607,25 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
var meetsConstraints = true;
|
var meetsConstraints = true;
|
||||||
foreach (var constraint in method.GenericParameters[0].Constraints)
|
foreach (var constraint in method.GenericParameters[0].Constraints)
|
||||||
{
|
{
|
||||||
|
#if CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES
|
||||||
var resolvedConstraint = constraint.Resolve();
|
var resolvedConstraint = constraint.Resolve();
|
||||||
|
var constraintTypeRef = constraint;
|
||||||
|
#else
|
||||||
|
var resolvedConstraint = constraint.ConstraintType.Resolve();
|
||||||
|
var constraintTypeRef = constraint.ConstraintType;
|
||||||
|
#endif
|
||||||
|
|
||||||
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraint.FullName)) ||
|
var resolvedConstraintName = resolvedConstraint.FullNameWithGenericParameters(new[] { method.GenericParameters[0] }, new[] { checkType });
|
||||||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)) ||
|
if (constraintTypeRef.IsGenericInstance)
|
||||||
|
{
|
||||||
|
var genericConstraint = (GenericInstanceType)constraintTypeRef;
|
||||||
|
if (genericConstraint.HasGenericArguments && genericConstraint.GenericArguments[0].Resolve() != null)
|
||||||
|
{
|
||||||
|
resolvedConstraintName = constraintTypeRef.FullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraintName)) ||
|
||||||
|
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraintName)) ||
|
||||||
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
|
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
|
||||||
{
|
{
|
||||||
meetsConstraints = false;
|
meetsConstraints = false;
|
||||||
@@ -605,7 +636,14 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
if (meetsConstraints)
|
if (meetsConstraints)
|
||||||
{
|
{
|
||||||
var instanceMethod = new GenericInstanceMethod(method);
|
var instanceMethod = new GenericInstanceMethod(method);
|
||||||
instanceMethod.GenericArguments.Add(checkType);
|
if (paramType.IsArray)
|
||||||
|
{
|
||||||
|
instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
instanceMethod.GenericArguments.Add(paramType);
|
||||||
|
}
|
||||||
return instanceMethod;
|
return instanceMethod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -653,13 +691,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try NetworkSerializable first because INetworkSerializable may also be valid for WriteValueSafe
|
var typeMethod = GetFastBufferWriterWriteMethod(k_WriteValueMethodName, paramType);
|
||||||
// and that would cause boxing if so.
|
|
||||||
var typeMethod = GetFastBufferWriterWriteMethod("WriteNetworkSerializable", paramType);
|
|
||||||
if (typeMethod == null)
|
|
||||||
{
|
|
||||||
typeMethod = GetFastBufferWriterWriteMethod(k_WriteValueMethodName, paramType);
|
|
||||||
}
|
|
||||||
if (typeMethod != null)
|
if (typeMethod != null)
|
||||||
{
|
{
|
||||||
methodRef = m_MainModule.ImportReference(typeMethod);
|
methodRef = m_MainModule.ImportReference(typeMethod);
|
||||||
@@ -699,28 +731,67 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
var checkType = paramType.Resolve();
|
var checkType = paramType.Resolve();
|
||||||
if (paramType.IsArray)
|
if (paramType.IsArray)
|
||||||
{
|
{
|
||||||
checkType = paramType.GetElementType().Resolve();
|
checkType = ((ArrayType)paramType).ElementType.Resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve())
|
if (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve())
|
||||||
{
|
{
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (methodParam.Resolve() == paramType || methodParam.Resolve() == paramType.MakeByReferenceType().Resolve())
|
||||||
|
{
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
if (method.HasGenericParameters && method.GenericParameters.Count == 1)
|
if (method.HasGenericParameters && method.GenericParameters.Count == 1)
|
||||||
{
|
{
|
||||||
if (method.GenericParameters[0].HasConstraints)
|
if (method.GenericParameters[0].HasConstraints)
|
||||||
{
|
{
|
||||||
|
var meetsConstraints = true;
|
||||||
foreach (var constraint in method.GenericParameters[0].Constraints)
|
foreach (var constraint in method.GenericParameters[0].Constraints)
|
||||||
{
|
{
|
||||||
|
#if CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES
|
||||||
var resolvedConstraint = constraint.Resolve();
|
var resolvedConstraint = constraint.Resolve();
|
||||||
|
var constraintTypeRef = constraint;
|
||||||
|
#else
|
||||||
|
var resolvedConstraint = constraint.ConstraintType.Resolve();
|
||||||
|
var constraintTypeRef = constraint.ConstraintType;
|
||||||
|
#endif
|
||||||
|
|
||||||
if ((resolvedConstraint.IsInterface && checkType.HasInterface(resolvedConstraint.FullName)) ||
|
|
||||||
(resolvedConstraint.IsClass && checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)))
|
var resolvedConstraintName = resolvedConstraint.FullNameWithGenericParameters(new[] { method.GenericParameters[0] }, new[] { checkType });
|
||||||
|
if (constraintTypeRef.IsGenericInstance)
|
||||||
{
|
{
|
||||||
var instanceMethod = new GenericInstanceMethod(method);
|
var genericConstraint = (GenericInstanceType)constraintTypeRef;
|
||||||
instanceMethod.GenericArguments.Add(checkType);
|
if (genericConstraint.HasGenericArguments && genericConstraint.GenericArguments[0].Resolve() != null)
|
||||||
return instanceMethod;
|
{
|
||||||
|
resolvedConstraintName = constraintTypeRef.FullName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraintName)) ||
|
||||||
|
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraintName)) ||
|
||||||
|
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
|
||||||
|
{
|
||||||
|
meetsConstraints = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meetsConstraints)
|
||||||
|
{
|
||||||
|
var instanceMethod = new GenericInstanceMethod(method);
|
||||||
|
if (paramType.IsArray)
|
||||||
|
{
|
||||||
|
instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
instanceMethod.GenericArguments.Add(paramType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instanceMethod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,13 +822,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try NetworkSerializable first because INetworkSerializable may also be valid for ReadValueSafe
|
var typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
|
||||||
// and that would cause boxing if so.
|
|
||||||
var typeMethod = GetFastBufferReaderReadMethod("ReadNetworkSerializable", paramType);
|
|
||||||
if (typeMethod == null)
|
|
||||||
{
|
|
||||||
typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
|
|
||||||
}
|
|
||||||
if (typeMethod != null)
|
if (typeMethod != null)
|
||||||
{
|
{
|
||||||
methodRef = m_MainModule.ImportReference(typeMethod);
|
methodRef = m_MainModule.ImportReference(typeMethod);
|
||||||
@@ -1003,6 +1068,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
// bufferWriter.WriteValueSafe(isSet);
|
// bufferWriter.WriteValueSafe(isSet);
|
||||||
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
|
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
|
||||||
instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex));
|
instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex));
|
||||||
|
|
||||||
|
for (var i = 1; i < boolMethodRef.Parameters.Count; ++i)
|
||||||
|
{
|
||||||
|
var param = boolMethodRef.Parameters[i];
|
||||||
|
methodDefinition.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||||
|
int overloadParamLocalIdx = methodDefinition.Body.Variables.Count - 1;
|
||||||
|
instructions.Add(processor.Create(OpCodes.Ldloca, overloadParamLocalIdx));
|
||||||
|
instructions.Add(processor.Create(OpCodes.Initobj, param.ParameterType));
|
||||||
|
instructions.Add(processor.Create(OpCodes.Ldloc, overloadParamLocalIdx));
|
||||||
|
}
|
||||||
|
|
||||||
instructions.Add(processor.Create(OpCodes.Call, boolMethodRef));
|
instructions.Add(processor.Create(OpCodes.Call, boolMethodRef));
|
||||||
|
|
||||||
// if(isSet) {
|
// if(isSet) {
|
||||||
@@ -1055,11 +1131,38 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
{
|
{
|
||||||
instructions.Add(processor.Create(OpCodes.Ldc_I4_0));
|
instructions.Add(processor.Create(OpCodes.Ldc_I4_0));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (isExtensionMethod && methodRef.Parameters.Count > 2)
|
||||||
|
{
|
||||||
|
for (var i = 2; i < methodRef.Parameters.Count; ++i)
|
||||||
|
{
|
||||||
|
var param = methodRef.Parameters[i];
|
||||||
|
methodDefinition.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||||
|
int overloadParamLocalIdx = methodDefinition.Body.Variables.Count - 1;
|
||||||
|
instructions.Add(processor.Create(OpCodes.Ldloca, overloadParamLocalIdx));
|
||||||
|
instructions.Add(processor.Create(OpCodes.Initobj, param.ParameterType));
|
||||||
|
instructions.Add(processor.Create(OpCodes.Ldloc, overloadParamLocalIdx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isExtensionMethod && methodRef.Parameters.Count > 1)
|
||||||
|
{
|
||||||
|
for (var i = 1; i < methodRef.Parameters.Count; ++i)
|
||||||
|
{
|
||||||
|
var param = methodRef.Parameters[i];
|
||||||
|
methodDefinition.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||||
|
int overloadParamLocalIdx = methodDefinition.Body.Variables.Count - 1;
|
||||||
|
instructions.Add(processor.Create(OpCodes.Ldloca, overloadParamLocalIdx));
|
||||||
|
instructions.Add(processor.Create(OpCodes.Initobj, param.ParameterType));
|
||||||
|
instructions.Add(processor.Create(OpCodes.Ldloc, overloadParamLocalIdx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
instructions.Add(processor.Create(OpCodes.Call, methodRef));
|
instructions.Add(processor.Create(OpCodes.Call, methodRef));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_Diagnostics.AddError(methodDefinition, $"Don't know how to serialize {paramType.Name} - implement {nameof(INetworkSerializable)} or add an extension method for {nameof(FastBufferWriter)}.{k_WriteValueMethodName} to define serialization.");
|
m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,6 +1401,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
int isSetLocalIndex = rpcHandler.Body.Variables.Count - 1;
|
int isSetLocalIndex = rpcHandler.Body.Variables.Count - 1;
|
||||||
processor.Emit(OpCodes.Ldarga, 1);
|
processor.Emit(OpCodes.Ldarga, 1);
|
||||||
processor.Emit(OpCodes.Ldloca, isSetLocalIndex);
|
processor.Emit(OpCodes.Ldloca, isSetLocalIndex);
|
||||||
|
|
||||||
|
for (var i = 1; i < boolMethodRef.Parameters.Count; ++i)
|
||||||
|
{
|
||||||
|
var param = boolMethodRef.Parameters[i];
|
||||||
|
rpcHandler.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||||
|
int overloadParamLocalIdx = rpcHandler.Body.Variables.Count - 1;
|
||||||
|
processor.Emit(OpCodes.Ldloca, overloadParamLocalIdx);
|
||||||
|
processor.Emit(OpCodes.Initobj, param.ParameterType);
|
||||||
|
processor.Emit(OpCodes.Ldloc, overloadParamLocalIdx);
|
||||||
|
}
|
||||||
|
|
||||||
processor.Emit(OpCodes.Call, boolMethodRef);
|
processor.Emit(OpCodes.Call, boolMethodRef);
|
||||||
|
|
||||||
// paramType param = null;
|
// paramType param = null;
|
||||||
@@ -1331,11 +1445,38 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
{
|
{
|
||||||
processor.Emit(OpCodes.Ldc_I4_0);
|
processor.Emit(OpCodes.Ldc_I4_0);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (isExtensionMethod && methodRef.Parameters.Count > 2)
|
||||||
|
{
|
||||||
|
for (var i = 2; i < methodRef.Parameters.Count; ++i)
|
||||||
|
{
|
||||||
|
var param = methodRef.Parameters[i];
|
||||||
|
rpcHandler.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||||
|
int overloadParamLocalIdx = rpcHandler.Body.Variables.Count - 1;
|
||||||
|
processor.Emit(OpCodes.Ldloca, overloadParamLocalIdx);
|
||||||
|
processor.Emit(OpCodes.Initobj, param.ParameterType);
|
||||||
|
processor.Emit(OpCodes.Ldloc, overloadParamLocalIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isExtensionMethod && methodRef.Parameters.Count > 1)
|
||||||
|
{
|
||||||
|
for (var i = 1; i < methodRef.Parameters.Count; ++i)
|
||||||
|
{
|
||||||
|
var param = methodRef.Parameters[i];
|
||||||
|
rpcHandler.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||||
|
int overloadParamLocalIdx = rpcHandler.Body.Variables.Count - 1;
|
||||||
|
processor.Emit(OpCodes.Ldloca, overloadParamLocalIdx);
|
||||||
|
processor.Emit(OpCodes.Initobj, param.ParameterType);
|
||||||
|
processor.Emit(OpCodes.Ldloc, overloadParamLocalIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
processor.Emit(OpCodes.Call, methodRef);
|
processor.Emit(OpCodes.Call, methodRef);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_Diagnostics.AddError(methodDefinition, $"Don't know how to deserialize {paramType.Name} - implement {nameof(INetworkSerializable)} or add an extension method for {nameof(FastBufferReader)}.{k_ReadValueMethodName} to define serialization.");
|
m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
case nameof(NetworkBehaviour):
|
case nameof(NetworkBehaviour):
|
||||||
ProcessNetworkBehaviour(typeDefinition);
|
ProcessNetworkBehaviour(typeDefinition);
|
||||||
break;
|
break;
|
||||||
case nameof(NetworkVariableHelper):
|
|
||||||
ProcessNetworkVariableHelper(typeDefinition);
|
|
||||||
break;
|
|
||||||
case nameof(__RpcParams):
|
case nameof(__RpcParams):
|
||||||
typeDefinition.IsPublic = true;
|
typeDefinition.IsPublic = true;
|
||||||
break;
|
break;
|
||||||
@@ -103,17 +100,6 @@ 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)
|
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
|
||||||
{
|
{
|
||||||
foreach (var nestedType in typeDefinition.NestedTypes)
|
foreach (var nestedType in typeDefinition.NestedTypes)
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
"name": "Unity.Netcode.Editor.CodeGen",
|
"name": "Unity.Netcode.Editor.CodeGen",
|
||||||
"rootNamespace": "Unity.Netcode.Editor.CodeGen",
|
"rootNamespace": "Unity.Netcode.Editor.CodeGen",
|
||||||
"references": [
|
"references": [
|
||||||
"Unity.Netcode.Runtime"
|
"Unity.Netcode.Runtime",
|
||||||
|
"Unity.Collections"
|
||||||
],
|
],
|
||||||
"includePlatforms": [
|
"includePlatforms": [
|
||||||
"Editor"
|
"Editor"
|
||||||
],
|
],
|
||||||
|
"excludePlatforms": [],
|
||||||
"allowUnsafeCode": true,
|
"allowUnsafeCode": true,
|
||||||
"overrideReferences": true,
|
"overrideReferences": true,
|
||||||
"precompiledReferences": [
|
"precompiledReferences": [
|
||||||
@@ -15,5 +17,14 @@
|
|||||||
"Mono.Cecil.Pdb.dll",
|
"Mono.Cecil.Pdb.dll",
|
||||||
"Mono.Cecil.Rocks.dll"
|
"Mono.Cecil.Rocks.dll"
|
||||||
],
|
],
|
||||||
"autoReferenced": false
|
"autoReferenced": false,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [
|
||||||
|
{
|
||||||
|
"name": "com.unity.nuget.mono-cecil",
|
||||||
|
"expression": "(0,1.11.4)",
|
||||||
|
"define": "CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"noEngineReferences": false
|
||||||
}
|
}
|
||||||
@@ -218,6 +218,11 @@ namespace Unity.Netcode.Editor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
|
// This can be null and throw an exception when running test runner in the editor
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
// When we first add a NetworkBehaviour this editor will be enabled
|
// When we first add a NetworkBehaviour this editor will be enabled
|
||||||
// so we go ahead and check for an already existing NetworkObject here
|
// so we go ahead and check for an already existing NetworkObject here
|
||||||
CheckForNetworkObject((target as NetworkBehaviour).gameObject);
|
CheckForNetworkObject((target as NetworkBehaviour).gameObject);
|
||||||
@@ -249,6 +254,39 @@ namespace Unity.Netcode.Editor
|
|||||||
|
|
||||||
// Now get the root parent transform to the current GameObject (or itself)
|
// Now get the root parent transform to the current GameObject (or itself)
|
||||||
var rootTransform = GetRootParentTransform(gameObject.transform);
|
var rootTransform = GetRootParentTransform(gameObject.transform);
|
||||||
|
var networkManager = rootTransform.GetComponent<NetworkManager>();
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
networkManager = rootTransform.GetComponentInChildren<NetworkManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a NetworkManager, then notify the user that a NetworkManager cannot have NetworkBehaviour components
|
||||||
|
if (networkManager != null)
|
||||||
|
{
|
||||||
|
var networkBehaviours = networkManager.gameObject.GetComponents<NetworkBehaviour>();
|
||||||
|
var networkBehavioursChildren = networkManager.gameObject.GetComponentsInChildren<NetworkBehaviour>();
|
||||||
|
if (networkBehaviours.Length > 0 || networkBehavioursChildren.Length > 0)
|
||||||
|
{
|
||||||
|
if (EditorUtility.DisplayDialog("NetworkBehaviour or NetworkManager Cannot Be Added", $"{nameof(NetworkManager)}s cannot have {nameof(NetworkBehaviour)} components added to the root parent or any of its children." +
|
||||||
|
$" Would you like to remove the NetworkManager or NetworkBehaviour?", "NetworkManager", "NetworkBehaviour"))
|
||||||
|
{
|
||||||
|
DestroyImmediate(networkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var networkBehaviour in networkBehaviours)
|
||||||
|
{
|
||||||
|
DestroyImmediate(networkBehaviour);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var networkBehaviour in networkBehaviours)
|
||||||
|
{
|
||||||
|
DestroyImmediate(networkBehaviour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children.
|
// Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children.
|
||||||
// If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component.
|
// If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component.
|
||||||
|
|||||||
@@ -363,7 +363,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 getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
|
||||||
const string openDocsButtonText = "Open Docs";
|
const string openDocsButtonText = "Open Docs";
|
||||||
const string dismissButtonText = "Dismiss";
|
const string dismissButtonText = "Dismiss";
|
||||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tools/install-tools";
|
const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools";
|
||||||
const string infoIconName = "console.infoicon";
|
const string infoIconName = "console.infoicon";
|
||||||
|
|
||||||
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
|
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
# Netcode for GameObjects
|
# Netcode for GameObjects
|
||||||
|
|
||||||
[](https://forum.unity.com/forums/multiplayer.26/) [](https://discord.gg/FM8SE9E)
|
[](https://forum.unity.com/forums/multiplayer.26/) [](https://discord.gg/FM8SE9E)
|
||||||
[](https://docs-multiplayer.unity3d.com/) [](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
[](https://docs-multiplayer.unity3d.com/netcode/current/about) [](https://docs-multiplayer.unity3d.com/netcode/current/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-multiplayer.unity3d.com/transport/1.0.0/introduction).
|
Netcode for GameObjects is a Unity package that provides networking capabilities to GameObject & MonoBehaviour workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs-multiplayer.unity3d.com/transport/current/about).
|
||||||
|
|
||||||
### Getting Started
|
### 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.
|
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.
|
||||||
|
|
||||||
|
You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro) guide for a taste of how to use the framework for basic networked tasks.
|
||||||
|
|
||||||
### Community and Feedback
|
### Community and Feedback
|
||||||
|
|
||||||
For general questions, networking advice or discussions about Netcode for GameObjects, please join our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/).
|
For general questions, networking advice or discussions about Netcode for GameObjects, please join our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/).
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
|||||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
||||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||||
|
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||||
#endif
|
#endif
|
||||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||||
|
|||||||
@@ -53,9 +53,21 @@ namespace Unity.Netcode
|
|||||||
public uint TickRate = 30;
|
public uint TickRate = 30;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of seconds to wait for handshake to complete before timing out a client
|
/// The amount of seconds for the server to wait for the connection approval handshake to complete before the client is disconnected.
|
||||||
|
///
|
||||||
|
/// If the timeout is reached before approval is completed the client will be disconnected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
|
/// <remarks>
|
||||||
|
/// The period begins after the <see cref="NetworkEvent.Connect"/> is received on the server.
|
||||||
|
/// The period ends once the server finishes processing a <see cref="ConnectionRequestMessage"/> from the client.
|
||||||
|
///
|
||||||
|
/// This setting is independent of any Transport-level timeouts that may be in effect. It covers the time between
|
||||||
|
/// the connection being established on the Transport layer, the client sending a
|
||||||
|
/// <see cref="ConnectionRequestMessage"/>, and the server processing that message through <see cref="ConnectionApproval"/>.
|
||||||
|
///
|
||||||
|
/// This setting is server-side only.
|
||||||
|
/// </remarks>
|
||||||
|
[Tooltip("The amount of seconds for the server to wait for the connection approval handshake to complete before the client is disconnected")]
|
||||||
public int ClientConnectionBufferTimeout = 10;
|
public int ClientConnectionBufferTimeout = 10;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -128,10 +140,10 @@ namespace Unity.Netcode
|
|||||||
public int LoadSceneTimeOut = 120;
|
public int LoadSceneTimeOut = 120;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
|
/// The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
|
[Tooltip("The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped")]
|
||||||
public float MessageBufferTimeout = 20f;
|
public float SpawnTimeout = 1f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not to enable network logs.
|
/// Whether or not to enable network logs.
|
||||||
|
|||||||
65
Runtime/Core/ComponentFactory.cs
Normal file
65
Runtime/Core/ComponentFactory.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is used to support testable code by allowing any supported component used by NetworkManager to be replaced
|
||||||
|
/// with a mock component or a test version that overloads certain methods to change or record their behavior.
|
||||||
|
/// Components currently supported by ComponentFactory:
|
||||||
|
/// - IDeferredMessageManager
|
||||||
|
/// </summary>
|
||||||
|
internal static class ComponentFactory
|
||||||
|
{
|
||||||
|
internal delegate object CreateObjectDelegate(NetworkManager networkManager);
|
||||||
|
|
||||||
|
private static Dictionary<Type, CreateObjectDelegate> s_Delegates = new Dictionary<Type, CreateObjectDelegate>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates an instance of a given interface
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="networkManager">The network manager</param>
|
||||||
|
/// <typeparam name="T">The interface to instantiate it with</typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T Create<T>(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
return (T)s_Delegates[typeof(T)](networkManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the default creation logic for a given interface type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="creator">The factory delegate to create the instance</param>
|
||||||
|
/// <typeparam name="T">The interface type to override</typeparam>
|
||||||
|
public static void Register<T>(CreateObjectDelegate creator)
|
||||||
|
{
|
||||||
|
s_Delegates[typeof(T)] = creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reverts the creation logic for a given interface type to the default logic
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The interface type to revert</typeparam>
|
||||||
|
public static void Deregister<T>()
|
||||||
|
{
|
||||||
|
s_Delegates.Remove(typeof(T));
|
||||||
|
SetDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the default creation logic for all supported component types
|
||||||
|
/// </summary>
|
||||||
|
public static void SetDefaults()
|
||||||
|
{
|
||||||
|
SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetDefault<T>(CreateObjectDelegate creator)
|
||||||
|
{
|
||||||
|
if (!s_Delegates.ContainsKey(typeof(T)))
|
||||||
|
{
|
||||||
|
s_Delegates[typeof(T)] = creator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Core/ComponentFactory.cs.meta
Normal file
3
Runtime/Core/ComponentFactory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fda4c0eb89644fcea5416bbf98ea0ba0
|
||||||
|
timeCreated: 1649966562
|
||||||
@@ -60,6 +60,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
private NetworkPrefabHandler m_PrefabHandler;
|
private NetworkPrefabHandler m_PrefabHandler;
|
||||||
|
|
||||||
|
internal Dictionary<ulong, ConnectionApprovalResponse> ClientsToApprove = new Dictionary<ulong, ConnectionApprovalResponse>();
|
||||||
|
|
||||||
public NetworkPrefabHandler PrefabHandler
|
public NetworkPrefabHandler PrefabHandler
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -122,7 +124,7 @@ namespace Unity.Netcode
|
|||||||
return !m_NetworkManager.m_StopProcessingMessages;
|
return !m_NetworkManager.m_StopProcessingMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||||
{
|
{
|
||||||
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
|
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
|
||||||
(client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
|
(client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
|
||||||
@@ -206,12 +208,14 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets if the application should be set to run in background
|
/// Gets or sets if the application should be set to run in background
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideInInspector] public bool RunInBackground = true;
|
[HideInInspector]
|
||||||
|
public bool RunInBackground = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The log level to use
|
/// The log level to use
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideInInspector] public LogLevel LogLevel = LogLevel.Normal;
|
[HideInInspector]
|
||||||
|
public LogLevel LogLevel = LogLevel.Normal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The singleton instance of the NetworkManager
|
/// The singleton instance of the NetworkManager
|
||||||
@@ -223,6 +227,8 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public NetworkSpawnManager SpawnManager { get; private set; }
|
public NetworkSpawnManager SpawnManager { get; private set; }
|
||||||
|
|
||||||
|
internal IDeferredMessageManager DeferredMessageManager { get; private set; }
|
||||||
|
|
||||||
public CustomMessagingManager CustomMessagingManager { get; private set; }
|
public CustomMessagingManager CustomMessagingManager { get; private set; }
|
||||||
|
|
||||||
public NetworkSceneManager SceneManager { get; private set; }
|
public NetworkSceneManager SceneManager { get; private set; }
|
||||||
@@ -356,26 +362,71 @@ namespace Unity.Netcode
|
|||||||
public event Action OnServerStarted = null;
|
public event Action OnServerStarted = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate type called when connection has been approved. This only has to be set on the server.
|
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="createPlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
|
/// <remarks>
|
||||||
/// <param name="playerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
|
/// A failure of the transport is always followed by the <see cref="NetworkManager"/> shutting down. Recovering
|
||||||
/// <param name="approved">Whether or not the client was approved</param>
|
/// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
|
||||||
/// <param name="position">The position to spawn the client at. If null, the prefab position is used.</param>
|
/// recreating a new service allocation depending on the transport) and restarting the client/server/host.
|
||||||
/// <param name="rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
|
/// </remarks>
|
||||||
public delegate void ConnectionApprovedDelegate(bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation);
|
public event Action OnTransportFailure = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The callback to invoke during connection approval
|
/// Connection Approval Response
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<byte[], ulong, ConnectionApprovedDelegate> ConnectionApprovalCallback = null;
|
/// <param name="Approved">Whether or not the client was approved</param>
|
||||||
|
/// <param name="CreatePlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
|
||||||
|
/// <param name="PlayerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
|
||||||
|
/// <param name="Position">The position to spawn the client at. If null, the prefab position is used.</param>
|
||||||
|
/// <param name="Rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
|
||||||
|
/// <param name="Pending">If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.</param>
|
||||||
|
public class ConnectionApprovalResponse
|
||||||
|
{
|
||||||
|
public bool Approved;
|
||||||
|
public bool CreatePlayerObject;
|
||||||
|
public uint? PlayerPrefabHash;
|
||||||
|
public Vector3? Position;
|
||||||
|
public Quaternion? Rotation;
|
||||||
|
public bool Pending;
|
||||||
|
}
|
||||||
|
|
||||||
internal void InvokeConnectionApproval(byte[] payload, ulong clientId, ConnectionApprovedDelegate action) => ConnectionApprovalCallback?.Invoke(payload, clientId, action);
|
/// <summary>
|
||||||
|
/// Connection Approval Request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Payload">The connection data payload</param>
|
||||||
|
/// <param name="ClientNetworkId">The Network Id of the client we are about to handle</param>
|
||||||
|
public struct ConnectionApprovalRequest
|
||||||
|
{
|
||||||
|
public byte[] Payload;
|
||||||
|
public ulong ClientNetworkId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
|
||||||
|
/// </summary>
|
||||||
|
public Action<ConnectionApprovalRequest, ConnectionApprovalResponse> ConnectionApprovalCallback
|
||||||
|
{
|
||||||
|
get => m_ConnectionApprovalCallback;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != null && value.GetInvocationList().Length > 1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Only one {nameof(ConnectionApprovalCallback)} can be registered at a time.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ConnectionApprovalCallback = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Action<ConnectionApprovalRequest, ConnectionApprovalResponse> m_ConnectionApprovalCallback;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current NetworkConfig
|
/// The current NetworkConfig
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideInInspector] public NetworkConfig NetworkConfig;
|
[HideInInspector]
|
||||||
|
public NetworkConfig NetworkConfig;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current host name we are connected to, used to validate certificate
|
/// The current host name we are connected to, used to validate certificate
|
||||||
@@ -387,7 +438,7 @@ namespace Unity.Netcode
|
|||||||
internal static event Action OnSingletonReady;
|
internal static event Action OnSingletonReady;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void OnValidate()
|
internal void OnValidate()
|
||||||
{
|
{
|
||||||
if (NetworkConfig == null)
|
if (NetworkConfig == null)
|
||||||
{
|
{
|
||||||
@@ -484,6 +535,294 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new prefab to the network prefab list.
|
||||||
|
/// This can be any GameObject with a NetworkObject component, from any source (addressables, asset
|
||||||
|
/// bundles, Resource.Load, dynamically created, etc)
|
||||||
|
///
|
||||||
|
/// There are three limitations to this method:
|
||||||
|
/// - If you have NetworkConfig.ForceSamePrefabs enabled, you can only do this before starting
|
||||||
|
/// networking, and the server and all connected clients must all have the same exact set of prefabs
|
||||||
|
/// added via this method before connecting
|
||||||
|
/// - Adding a prefab on the server does not automatically add it on the client - it's up to you
|
||||||
|
/// to make sure the client and server are synchronized via whatever method makes sense for your game
|
||||||
|
/// (RPCs, configs, deterministic loading, etc)
|
||||||
|
/// - If the server sends a Spawn message to a client that has not yet added a prefab for, the spawn message
|
||||||
|
/// and any other relevant messages will be held for a configurable time (default 1 second, configured via
|
||||||
|
/// NetworkConfig.SpawnTimeout) before an error is logged. This is intented to enable the SDK to gracefully
|
||||||
|
/// handle unexpected conditions (slow disks, slow network, etc) that slow down asset loading. This timeout
|
||||||
|
/// should not be relied on and code shouldn't be written around it - your code should be written so that
|
||||||
|
/// the asset is expected to be loaded before it's needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prefab"></param>
|
||||||
|
/// <exception cref="Exception"></exception>
|
||||||
|
public void AddNetworkPrefab(GameObject prefab)
|
||||||
|
{
|
||||||
|
if (IsListening && NetworkConfig.ForceSamePrefabs)
|
||||||
|
{
|
||||||
|
throw new Exception($"All prefabs must be registered before starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkObject = prefab.GetComponent<NetworkObject>();
|
||||||
|
if (!networkObject)
|
||||||
|
{
|
||||||
|
throw new Exception($"All {nameof(NetworkPrefab)}s must contain a {nameof(NetworkObject)} component.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkPrefab = new NetworkPrefab { Prefab = prefab };
|
||||||
|
NetworkConfig.NetworkPrefabs.Add(networkPrefab);
|
||||||
|
if (IsListening)
|
||||||
|
{
|
||||||
|
var sourcePrefabGlobalObjectIdHash = (uint)0;
|
||||||
|
var targetPrefabGlobalObjectIdHash = (uint)0;
|
||||||
|
if (!ShouldAddPrefab(networkPrefab, out sourcePrefabGlobalObjectIdHash, out targetPrefabGlobalObjectIdHash))
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabs.Remove(networkPrefab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AddPrefabRegistration(networkPrefab, sourcePrefabGlobalObjectIdHash, targetPrefabGlobalObjectIdHash))
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabs.Remove(networkPrefab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DeferredMessageManager.ProcessTriggers(IDeferredMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a prefab from the prefab list.
|
||||||
|
/// As with AddNetworkPrefab, this is specific to the client it's called on -
|
||||||
|
/// calling it on the server does not automatically remove anything on any of the
|
||||||
|
/// client processes.
|
||||||
|
///
|
||||||
|
/// Like AddNetworkPrefab, when NetworkConfig.ForceSamePrefabs is enabled,
|
||||||
|
/// this cannot be called after connecting.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prefab"></param>
|
||||||
|
public void RemoveNetworkPrefab(GameObject prefab)
|
||||||
|
{
|
||||||
|
if (IsListening && NetworkConfig.ForceSamePrefabs)
|
||||||
|
{
|
||||||
|
throw new Exception($"Prefabs cannot be removed after starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalObjectIdHash = prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||||
|
for (var i = 0; i < NetworkConfig.NetworkPrefabs.Count; ++i)
|
||||||
|
{
|
||||||
|
if (NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash == globalObjectIdHash)
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabs.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||||
|
{
|
||||||
|
PrefabHandler.RemoveHandler(globalObjectIdHash);
|
||||||
|
}
|
||||||
|
if (NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, out var targetPrefab))
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabOverrideLinks.Remove(globalObjectIdHash);
|
||||||
|
var targetHash = targetPrefab.Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||||
|
if (NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetHash))
|
||||||
|
{
|
||||||
|
NetworkConfig.OverrideToNetworkPrefab.Remove(targetHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldAddPrefab(NetworkPrefab networkPrefab, out uint sourcePrefabGlobalObjectIdHash, out uint targetPrefabGlobalObjectIdHash, int index = -1)
|
||||||
|
{
|
||||||
|
sourcePrefabGlobalObjectIdHash = 0;
|
||||||
|
targetPrefabGlobalObjectIdHash = 0;
|
||||||
|
var networkObject = (NetworkObject)null;
|
||||||
|
if (networkPrefab == null || (networkPrefab.Prefab == null && networkPrefab.Override == NetworkPrefabOverride.None))
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning(
|
||||||
|
$"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {index})");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (networkPrefab.Override == NetworkPrefabOverride.None)
|
||||||
|
{
|
||||||
|
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||||
|
if (networkObject == null)
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} is missing " +
|
||||||
|
$"a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise get the GlobalObjectIdHash value
|
||||||
|
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
||||||
|
}
|
||||||
|
else // Validate Overrides
|
||||||
|
{
|
||||||
|
// Validate source prefab override values first
|
||||||
|
switch (networkPrefab.Override)
|
||||||
|
{
|
||||||
|
case NetworkPrefabOverride.Hash:
|
||||||
|
{
|
||||||
|
if (networkPrefab.SourceHashToOverride == 0)
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourceHashToOverride)} is zero " +
|
||||||
|
"(entry will be ignored).");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sourcePrefabGlobalObjectIdHash = networkPrefab.SourceHashToOverride;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NetworkPrefabOverride.Prefab:
|
||||||
|
{
|
||||||
|
if (networkPrefab.SourcePrefabToOverride == null)
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourcePrefabToOverride)} is null (entry will be ignored).");
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {networkPrefab.SourceHashToOverride} will be removed and ignored.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
networkObject = networkPrefab.SourcePrefabToOverride.GetComponent<NetworkObject>();
|
||||||
|
if (networkObject == null)
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({networkPrefab.SourcePrefabToOverride.name}) " +
|
||||||
|
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry (\"{networkPrefab.SourcePrefabToOverride.name}\") will be removed and ignored.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate target prefab override values next
|
||||||
|
if (networkPrefab.OverridingTargetPrefab == null)
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.OverridingTargetPrefab)} is null!");
|
||||||
|
}
|
||||||
|
switch (networkPrefab.Override)
|
||||||
|
{
|
||||||
|
case NetworkPrefabOverride.Hash:
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {networkPrefab.SourceHashToOverride} will be removed and ignored.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NetworkPrefabOverride.Prefab:
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({networkPrefab.SourcePrefabToOverride.name}) will be removed and ignored.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetPrefabGlobalObjectIdHash = networkPrefab.OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool AddPrefabRegistration(NetworkPrefab networkPrefab, uint sourcePrefabGlobalObjectIdHash, uint targetPrefabGlobalObjectIdHash)
|
||||||
|
{
|
||||||
|
// Assign the appropriate GlobalObjectIdHash to the appropriate NetworkPrefab
|
||||||
|
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(sourcePrefabGlobalObjectIdHash))
|
||||||
|
{
|
||||||
|
if (networkPrefab.Override == NetworkPrefabOverride.None)
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetPrefabGlobalObjectIdHash))
|
||||||
|
{
|
||||||
|
switch (networkPrefab.Override)
|
||||||
|
{
|
||||||
|
case NetworkPrefabOverride.Prefab:
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
|
||||||
|
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetworkPrefabOverride.Hash:
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
|
||||||
|
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||||
|
// This can happen if a user tries to make several GlobalObjectIdHash values point to the same target
|
||||||
|
Debug.LogError($"{nameof(NetworkPrefab)} (\"{networkObject.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} target entry value of: {targetPrefabGlobalObjectIdHash}! Removing entry from list!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||||
|
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
|
||||||
|
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {sourcePrefabGlobalObjectIdHash}! Removing entry from list!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializePrefabs(int startIdx = 0)
|
||||||
|
{
|
||||||
|
// This is used to remove entries not needed or invalid
|
||||||
|
var removeEmptyPrefabs = new List<int>();
|
||||||
|
|
||||||
|
// Build the NetworkPrefabOverrideLinks dictionary
|
||||||
|
for (int i = startIdx; i < NetworkConfig.NetworkPrefabs.Count; i++)
|
||||||
|
{
|
||||||
|
var sourcePrefabGlobalObjectIdHash = (uint)0;
|
||||||
|
var targetPrefabGlobalObjectIdHash = (uint)0;
|
||||||
|
if (!ShouldAddPrefab(NetworkConfig.NetworkPrefabs[i], out sourcePrefabGlobalObjectIdHash, out targetPrefabGlobalObjectIdHash, i))
|
||||||
|
{
|
||||||
|
removeEmptyPrefabs.Add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AddPrefabRegistration(NetworkConfig.NetworkPrefabs[i], sourcePrefabGlobalObjectIdHash, targetPrefabGlobalObjectIdHash))
|
||||||
|
{
|
||||||
|
removeEmptyPrefabs.Add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out anything that is invalid or not used (for invalid entries we already logged warnings to the user earlier)
|
||||||
|
// Iterate backwards so indices don't shift as we remove
|
||||||
|
for (int i = removeEmptyPrefabs.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
NetworkConfig.NetworkPrefabs.RemoveAt(removeEmptyPrefabs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEmptyPrefabs.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
private void Initialize(bool server)
|
private void Initialize(bool server)
|
||||||
{
|
{
|
||||||
@@ -494,6 +833,8 @@ namespace Unity.Netcode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComponentFactory.SetDefaults();
|
||||||
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||||
{
|
{
|
||||||
NetworkLog.LogInfo(nameof(Initialize));
|
NetworkLog.LogInfo(nameof(Initialize));
|
||||||
@@ -514,16 +855,13 @@ namespace Unity.Netcode
|
|||||||
#endif
|
#endif
|
||||||
LocalClientId = ulong.MaxValue;
|
LocalClientId = ulong.MaxValue;
|
||||||
|
|
||||||
PendingClients.Clear();
|
ClearClients();
|
||||||
m_ConnectedClients.Clear();
|
|
||||||
m_ConnectedClientsList.Clear();
|
|
||||||
m_ConnectedClientIds.Clear();
|
|
||||||
LocalClient = null;
|
|
||||||
NetworkObject.OrphanChildren.Clear();
|
|
||||||
|
|
||||||
// Create spawn manager instance
|
// Create spawn manager instance
|
||||||
SpawnManager = new NetworkSpawnManager(this);
|
SpawnManager = new NetworkSpawnManager(this);
|
||||||
|
|
||||||
|
DeferredMessageManager = ComponentFactory.Create<IDeferredMessageManager>(this);
|
||||||
|
|
||||||
CustomMessagingManager = new CustomMessagingManager(this);
|
CustomMessagingManager = new CustomMessagingManager(this);
|
||||||
|
|
||||||
SceneManager = new NetworkSceneManager(this);
|
SceneManager = new NetworkSceneManager(this);
|
||||||
@@ -573,171 +911,11 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
|
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
|
||||||
|
|
||||||
// This is used to remove entries not needed or invalid
|
|
||||||
var removeEmptyPrefabs = new List<int>();
|
|
||||||
|
|
||||||
// Always clear our prefab override links before building
|
// Always clear our prefab override links before building
|
||||||
NetworkConfig.NetworkPrefabOverrideLinks.Clear();
|
NetworkConfig.NetworkPrefabOverrideLinks.Clear();
|
||||||
NetworkConfig.OverrideToNetworkPrefab.Clear();
|
NetworkConfig.OverrideToNetworkPrefab.Clear();
|
||||||
|
|
||||||
// Build the NetworkPrefabOverrideLinks dictionary
|
InitializePrefabs();
|
||||||
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++)
|
|
||||||
{
|
|
||||||
var sourcePrefabGlobalObjectIdHash = (uint)0;
|
|
||||||
var targetPrefabGlobalObjectIdHash = (uint)0;
|
|
||||||
var networkObject = (NetworkObject)null;
|
|
||||||
if (NetworkConfig.NetworkPrefabs[i] == null || (NetworkConfig.NetworkPrefabs[i].Prefab == null && NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None))
|
|
||||||
{
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning(
|
|
||||||
$"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {i})");
|
|
||||||
}
|
|
||||||
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None)
|
|
||||||
{
|
|
||||||
var networkPrefab = NetworkConfig.NetworkPrefabs[i];
|
|
||||||
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
|
||||||
if (networkObject == null)
|
|
||||||
{
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} is missing " +
|
|
||||||
$"a {nameof(NetworkObject)} component (entry will be ignored).");
|
|
||||||
}
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise get the GlobalObjectIdHash value
|
|
||||||
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
|
||||||
}
|
|
||||||
else // Validate Overrides
|
|
||||||
{
|
|
||||||
// Validate source prefab override values first
|
|
||||||
switch (NetworkConfig.NetworkPrefabs[i].Override)
|
|
||||||
{
|
|
||||||
case NetworkPrefabOverride.Hash:
|
|
||||||
{
|
|
||||||
if (NetworkConfig.NetworkPrefabs[i].SourceHashToOverride == 0)
|
|
||||||
{
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourceHashToOverride)} is zero " +
|
|
||||||
"(entry will be ignored).");
|
|
||||||
}
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sourcePrefabGlobalObjectIdHash = NetworkConfig.NetworkPrefabs[i].SourceHashToOverride;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NetworkPrefabOverride.Prefab:
|
|
||||||
{
|
|
||||||
if (NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride == null)
|
|
||||||
{
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourcePrefabToOverride)} is null (entry will be ignored).");
|
|
||||||
}
|
|
||||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {NetworkConfig.NetworkPrefabs[i].SourceHashToOverride} will be removed and ignored.");
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
networkObject = NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.GetComponent<NetworkObject>();
|
|
||||||
if (networkObject == null)
|
|
||||||
{
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}) " +
|
|
||||||
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
|
|
||||||
}
|
|
||||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry (\"{NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}\") will be removed and ignored.");
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate target prefab override values next
|
|
||||||
if (NetworkConfig.NetworkPrefabs[i].OverridingTargetPrefab == null)
|
|
||||||
{
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.OverridingTargetPrefab)} is null!");
|
|
||||||
}
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
switch (NetworkConfig.NetworkPrefabs[i].Override)
|
|
||||||
{
|
|
||||||
case NetworkPrefabOverride.Hash:
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {NetworkConfig.NetworkPrefabs[i].SourceHashToOverride} will be removed and ignored.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NetworkPrefabOverride.Prefab:
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}) will be removed and ignored.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
targetPrefabGlobalObjectIdHash = NetworkConfig.NetworkPrefabs[i].OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign the appropriate GlobalObjectIdHash to the appropriate NetworkPrefab
|
|
||||||
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(sourcePrefabGlobalObjectIdHash))
|
|
||||||
{
|
|
||||||
if (NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None)
|
|
||||||
{
|
|
||||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetPrefabGlobalObjectIdHash))
|
|
||||||
{
|
|
||||||
switch (NetworkConfig.NetworkPrefabs[i].Override)
|
|
||||||
{
|
|
||||||
case NetworkPrefabOverride.Prefab:
|
|
||||||
{
|
|
||||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
|
|
||||||
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NetworkPrefabOverride.Hash:
|
|
||||||
{
|
|
||||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
|
|
||||||
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This can happen if a user tries to make several GlobalObjectIdHash values point to the same target
|
|
||||||
Debug.LogError($"{nameof(NetworkPrefab)} (\"{networkObject.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} target entry value of: {targetPrefabGlobalObjectIdHash}! Removing entry from list!");
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
|
|
||||||
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {sourcePrefabGlobalObjectIdHash}! Removing entry from list!");
|
|
||||||
removeEmptyPrefabs.Add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
|
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
|
||||||
if (NetworkConfig.PlayerPrefab != null)
|
if (NetworkConfig.PlayerPrefab != null)
|
||||||
@@ -764,20 +942,22 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear out anything that is invalid or not used (for invalid entries we already logged warnings to the user earlier)
|
|
||||||
// Iterate backwards so indices don't shift as we remove
|
|
||||||
for (int i = removeEmptyPrefabs.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
NetworkConfig.NetworkPrefabs.RemoveAt(removeEmptyPrefabs[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeEmptyPrefabs.Clear();
|
|
||||||
|
|
||||||
NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
|
NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
|
||||||
|
|
||||||
NetworkConfig.NetworkTransport.Initialize(this);
|
NetworkConfig.NetworkTransport.Initialize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearClients()
|
||||||
|
{
|
||||||
|
PendingClients.Clear();
|
||||||
|
m_ConnectedClients.Clear();
|
||||||
|
m_ConnectedClientsList.Clear();
|
||||||
|
m_ConnectedClientIds.Clear();
|
||||||
|
LocalClient = null;
|
||||||
|
NetworkObject.OrphanChildren.Clear();
|
||||||
|
ClientsToApprove.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts a server
|
/// Starts a server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -810,6 +990,7 @@ namespace Unity.Netcode
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||||
|
OnTransportFailure?.Invoke();
|
||||||
Shutdown();
|
Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,6 +1018,7 @@ namespace Unity.Netcode
|
|||||||
if (!NetworkConfig.NetworkTransport.StartClient())
|
if (!NetworkConfig.NetworkTransport.StartClient())
|
||||||
{
|
{
|
||||||
Debug.LogError($"Client is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
Debug.LogError($"Client is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||||
|
OnTransportFailure?.Invoke();
|
||||||
Shutdown();
|
Shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -869,6 +1051,7 @@ namespace Unity.Netcode
|
|||||||
if (!NetworkConfig.NetworkTransport.StartServer())
|
if (!NetworkConfig.NetworkTransport.StartServer())
|
||||||
{
|
{
|
||||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||||
|
OnTransportFailure?.Invoke();
|
||||||
Shutdown();
|
Shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -881,27 +1064,29 @@ namespace Unity.Netcode
|
|||||||
IsClient = true;
|
IsClient = true;
|
||||||
IsListening = true;
|
IsListening = true;
|
||||||
|
|
||||||
if (NetworkConfig.ConnectionApproval)
|
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
|
||||||
{
|
{
|
||||||
InvokeConnectionApproval(NetworkConfig.ConnectionData, ServerClientId,
|
var response = new ConnectionApprovalResponse();
|
||||||
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
|
ConnectionApprovalCallback(new ConnectionApprovalRequest { Payload = NetworkConfig.ConnectionData, ClientNetworkId = ServerClientId }, response);
|
||||||
|
if (!response.Approved)
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
{
|
{
|
||||||
// You cannot decline the local server. Force approved to true
|
NetworkLog.LogWarning("You cannot decline the host connection. The connection was automatically approved.");
|
||||||
if (!approved)
|
}
|
||||||
{
|
}
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning(
|
|
||||||
"You cannot decline the host connection. The connection was automatically approved.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleApproval(ServerClientId, createPlayerObject, playerPrefabHash, true, position, rotation);
|
response.Approved = true;
|
||||||
});
|
HandleConnectionApproval(ServerClientId, response);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleApproval(ServerClientId, NetworkConfig.PlayerPrefab != null, null, true, null, null);
|
var response = new ConnectionApprovalResponse
|
||||||
|
{
|
||||||
|
Approved = true,
|
||||||
|
CreatePlayerObject = NetworkConfig.PlayerPrefab != null
|
||||||
|
};
|
||||||
|
HandleConnectionApproval(ServerClientId, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||||
@@ -930,7 +1115,9 @@ namespace Unity.Netcode
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkConfig.ConnectionApproval)
|
// Only if it is starting as a server or host do we need to check this
|
||||||
|
// Clients don't invoke the ConnectionApprovalCallback
|
||||||
|
if (NetworkConfig.ConnectionApproval && type != StartType.Client)
|
||||||
{
|
{
|
||||||
if (ConnectionApprovalCallback == null)
|
if (ConnectionApprovalCallback == null)
|
||||||
{
|
{
|
||||||
@@ -1081,8 +1268,15 @@ namespace Unity.Netcode
|
|||||||
NetworkLog.LogInfo(nameof(Shutdown));
|
NetworkLog.LogInfo(nameof(Shutdown));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ShuttingDown = true;
|
// If we're not running, don't start shutting down, it would only cause an immediate
|
||||||
m_StopProcessingMessages = discardMessageQueue;
|
// shutdown the next time the manager is started.
|
||||||
|
if (IsServer || IsClient)
|
||||||
|
{
|
||||||
|
m_ShuttingDown = true;
|
||||||
|
m_StopProcessingMessages = discardMessageQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ShutdownInternal()
|
internal void ShutdownInternal()
|
||||||
@@ -1165,13 +1359,17 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (SpawnManager != null)
|
if (SpawnManager != null)
|
||||||
{
|
{
|
||||||
SpawnManager.CleanupAllTriggers();
|
|
||||||
SpawnManager.DespawnAndDestroyNetworkObjects();
|
SpawnManager.DespawnAndDestroyNetworkObjects();
|
||||||
SpawnManager.ServerResetShudownStateForSceneObjects();
|
SpawnManager.ServerResetShudownStateForSceneObjects();
|
||||||
|
|
||||||
SpawnManager = null;
|
SpawnManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DeferredMessageManager != null)
|
||||||
|
{
|
||||||
|
DeferredMessageManager.CleanupAllTriggers();
|
||||||
|
}
|
||||||
|
|
||||||
if (SceneManager != null)
|
if (SceneManager != null)
|
||||||
{
|
{
|
||||||
// Let the NetworkSceneManager clean up its two SceneEvenData instances
|
// Let the NetworkSceneManager clean up its two SceneEvenData instances
|
||||||
@@ -1203,6 +1401,8 @@ namespace Unity.Netcode
|
|||||||
IsListening = false;
|
IsListening = false;
|
||||||
m_ShuttingDown = false;
|
m_ShuttingDown = false;
|
||||||
m_StopProcessingMessages = false;
|
m_StopProcessingMessages = false;
|
||||||
|
|
||||||
|
ClearClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
// INetworkUpdateSystem
|
// INetworkUpdateSystem
|
||||||
@@ -1222,6 +1422,43 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessPendingApprovals()
|
||||||
|
{
|
||||||
|
List<ulong> senders = null;
|
||||||
|
|
||||||
|
foreach (var responsePair in ClientsToApprove)
|
||||||
|
{
|
||||||
|
var response = responsePair.Value;
|
||||||
|
var senderId = responsePair.Key;
|
||||||
|
|
||||||
|
if (!response.Pending)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HandleConnectionApproval(senderId, response);
|
||||||
|
|
||||||
|
if (senders == null)
|
||||||
|
{
|
||||||
|
senders = new List<ulong>();
|
||||||
|
}
|
||||||
|
senders.Add(senderId);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senders != null)
|
||||||
|
{
|
||||||
|
foreach (var sender in senders)
|
||||||
|
{
|
||||||
|
ClientsToApprove.Remove(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnNetworkEarlyUpdate()
|
private void OnNetworkEarlyUpdate()
|
||||||
{
|
{
|
||||||
if (!IsListening)
|
if (!IsListening)
|
||||||
@@ -1229,6 +1466,8 @@ namespace Unity.Netcode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProcessPendingApprovals();
|
||||||
|
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
s_TransportPoll.Begin();
|
s_TransportPoll.Begin();
|
||||||
#endif
|
#endif
|
||||||
@@ -1287,7 +1526,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
NetworkObject.VerifyParentingStatus();
|
NetworkObject.VerifyParentingStatus();
|
||||||
}
|
}
|
||||||
SpawnManager.CleanupStaleTriggers();
|
DeferredMessageManager.CleanupStaleTriggers();
|
||||||
|
|
||||||
if (m_ShuttingDown)
|
if (m_ShuttingDown)
|
||||||
{
|
{
|
||||||
@@ -1415,11 +1654,6 @@ namespace Unity.Netcode
|
|||||||
break;
|
break;
|
||||||
case NetworkEvent.Data:
|
case NetworkEvent.Data:
|
||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
|
||||||
{
|
|
||||||
NetworkLog.LogInfo($"Incoming Data From {clientId}: {payload.Count} bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
clientId = TransportIdToClientId(clientId);
|
clientId = TransportIdToClientId(clientId);
|
||||||
|
|
||||||
HandleIncomingData(clientId, payload, receiveTime);
|
HandleIncomingData(clientId, payload, receiveTime);
|
||||||
@@ -1444,12 +1678,22 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Shutdown();
|
// We must pass true here and not process any sends messages
|
||||||
|
// as we are no longer connected and thus there is no one to
|
||||||
|
// send any messages to and this will cause an exception within
|
||||||
|
// UnityTransport as the client ID is no longer valid.
|
||||||
|
Shutdown(true);
|
||||||
}
|
}
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
s_TransportDisconnect.End();
|
s_TransportDisconnect.End();
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case NetworkEvent.TransportFailure:
|
||||||
|
Debug.LogError($"Shutting down due to network transport failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||||
|
OnTransportFailure?.Invoke();
|
||||||
|
Shutdown(true);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1714,15 +1958,11 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server Side: Handles the approval of a client
|
/// Server Side: Handles the approval of a client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ownerClientId">client being approved</param>
|
/// <param name="ownerClientId">The Network Id of the client being approved</param>
|
||||||
/// <param name="createPlayerObject">whether we want to create a player or not</param>
|
/// <param name="response">The response to allow the player in or not, with its parameters</param>
|
||||||
/// <param name="playerPrefabHash">the GlobalObjectIdHash value for the Network Prefab to create as the player</param>
|
internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalResponse response)
|
||||||
/// <param name="approved">Is the player approved or not?</param>
|
|
||||||
/// <param name="position">Used when createPlayerObject is true, position of the player when spawned </param>
|
|
||||||
/// <param name="rotation">Used when createPlayerObject is true, rotation of the player when spawned</param>
|
|
||||||
internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation)
|
|
||||||
{
|
{
|
||||||
if (approved)
|
if (response.Approved)
|
||||||
{
|
{
|
||||||
// Inform new client it got approved
|
// Inform new client it got approved
|
||||||
PendingClients.Remove(ownerClientId);
|
PendingClients.Remove(ownerClientId);
|
||||||
@@ -1732,10 +1972,23 @@ namespace Unity.Netcode
|
|||||||
m_ConnectedClientsList.Add(client);
|
m_ConnectedClientsList.Add(client);
|
||||||
m_ConnectedClientIds.Add(client.ClientId);
|
m_ConnectedClientIds.Add(client.ClientId);
|
||||||
|
|
||||||
if (createPlayerObject)
|
if (response.CreatePlayerObject)
|
||||||
{
|
{
|
||||||
var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, null, position, rotation);
|
var networkObject = SpawnManager.CreateLocalNetworkObject(
|
||||||
SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, false);
|
isSceneObject: false,
|
||||||
|
response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash,
|
||||||
|
ownerClientId,
|
||||||
|
parentNetworkId: null,
|
||||||
|
networkSceneHandle: null,
|
||||||
|
response.Position,
|
||||||
|
response.Rotation);
|
||||||
|
SpawnManager.SpawnNetworkObjectLocally(
|
||||||
|
networkObject,
|
||||||
|
SpawnManager.GetNetworkObjectId(),
|
||||||
|
sceneObject: false,
|
||||||
|
playerObject: true,
|
||||||
|
ownerClientId,
|
||||||
|
destroyWithScene: false);
|
||||||
|
|
||||||
ConnectedClients[ownerClientId].PlayerObject = networkObject;
|
ConnectedClients[ownerClientId].PlayerObject = networkObject;
|
||||||
}
|
}
|
||||||
@@ -1775,13 +2028,13 @@ namespace Unity.Netcode
|
|||||||
InvokeOnClientConnectedCallback(ownerClientId);
|
InvokeOnClientConnectedCallback(ownerClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
|
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separating this into a contained function call for potential further future separation of when this notification is sent.
|
// Separating this into a contained function call for potential further future separation of when this notification is sent.
|
||||||
ApprovedPlayerSpawn(ownerClientId, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
|
ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -75,7 +76,7 @@ namespace Unity.Netcode
|
|||||||
public bool IsPlayerObject { get; internal set; }
|
public bool IsPlayerObject { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets if the object is the the personal clients player object
|
/// Gets if the object is the personal clients player object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsLocalPlayer => NetworkManager != null && IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId;
|
public bool IsLocalPlayer => NetworkManager != null && IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId;
|
||||||
|
|
||||||
@@ -151,6 +152,7 @@ namespace Unity.Netcode
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly HashSet<ulong> m_EmptyULongHashSet = new HashSet<ulong>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns Observers enumerator
|
/// Returns Observers enumerator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -159,7 +161,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (!IsSpawned)
|
if (!IsSpawned)
|
||||||
{
|
{
|
||||||
throw new SpawnStateException("Object is not spawned");
|
return m_EmptyULongHashSet.GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Observers.GetEnumerator();
|
return Observers.GetEnumerator();
|
||||||
@@ -174,15 +176,62 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (!IsSpawned)
|
if (!IsSpawned)
|
||||||
{
|
{
|
||||||
throw new SpawnStateException("Object is not spawned");
|
return false;
|
||||||
|
}
|
||||||
|
return Observers.Contains(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In the event the scene of origin gets unloaded, we keep
|
||||||
|
/// the most important part to uniquely identify in-scene
|
||||||
|
/// placed NetworkObjects
|
||||||
|
/// </summary>
|
||||||
|
internal int SceneOriginHandle = 0;
|
||||||
|
|
||||||
|
private Scene m_SceneOrigin;
|
||||||
|
/// <summary>
|
||||||
|
/// The scene where the NetworkObject was first instantiated
|
||||||
|
/// Note: Primarily for in-scene placed NetworkObjects
|
||||||
|
/// We need to keep track of the original scene of origin for
|
||||||
|
/// the NetworkObject in order to be able to uniquely identify it
|
||||||
|
/// using the scene of origin's handle.
|
||||||
|
/// </summary>
|
||||||
|
internal Scene SceneOrigin
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_SceneOrigin;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Observers.Contains(clientId);
|
set
|
||||||
|
{
|
||||||
|
// The scene origin should only be set once.
|
||||||
|
// Once set, it should never change.
|
||||||
|
if (SceneOriginHandle == 0 && value.IsValid() && value.isLoaded)
|
||||||
|
{
|
||||||
|
m_SceneOrigin = value;
|
||||||
|
SceneOriginHandle = value.handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to return the correct scene handle
|
||||||
|
/// Note: Do not use this within NetworkSpawnManager.SpawnNetworkObjectLocallyCommon
|
||||||
|
/// </summary>
|
||||||
|
internal int GetSceneOriginHandle()
|
||||||
|
{
|
||||||
|
if (SceneOriginHandle == 0 && IsSpawned && IsSceneObject != false)
|
||||||
|
{
|
||||||
|
throw new Exception($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
|
||||||
|
}
|
||||||
|
return SceneOriginHandle != 0 ? SceneOriginHandle : gameObject.scene.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
SetCachedParent(transform.parent);
|
SetCachedParent(transform.parent);
|
||||||
|
SceneOrigin = gameObject.scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -285,7 +334,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
var message = new DestroyObjectMessage
|
var message = new DestroyObjectMessage
|
||||||
{
|
{
|
||||||
NetworkObjectId = NetworkObjectId
|
NetworkObjectId = NetworkObjectId,
|
||||||
|
DestroyGameObject = true
|
||||||
};
|
};
|
||||||
// Send destroy call
|
// Send destroy call
|
||||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||||
@@ -352,7 +402,10 @@ namespace Unity.Netcode
|
|||||||
if (NetworkManager != null && NetworkManager.SpawnManager != null &&
|
if (NetworkManager != null && NetworkManager.SpawnManager != null &&
|
||||||
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||||
{
|
{
|
||||||
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false);
|
if (this == networkObject)
|
||||||
|
{
|
||||||
|
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +422,7 @@ namespace Unity.Netcode
|
|||||||
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
|
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene);
|
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene);
|
||||||
|
|
||||||
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
|
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -826,7 +879,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal struct SceneObject
|
internal struct SceneObject
|
||||||
{
|
{
|
||||||
public struct HeaderData
|
public struct HeaderData : INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public ulong NetworkObjectId;
|
public ulong NetworkObjectId;
|
||||||
public ulong OwnerClientId;
|
public ulong OwnerClientId;
|
||||||
@@ -845,7 +898,7 @@ namespace Unity.Netcode
|
|||||||
public ulong ParentObjectId;
|
public ulong ParentObjectId;
|
||||||
|
|
||||||
//If(Metadata.HasTransform)
|
//If(Metadata.HasTransform)
|
||||||
public struct TransformData
|
public struct TransformData : INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
public Quaternion Rotation;
|
public Quaternion Rotation;
|
||||||
@@ -862,16 +915,17 @@ namespace Unity.Netcode
|
|||||||
public NetworkObject OwnerObject;
|
public NetworkObject OwnerObject;
|
||||||
public ulong TargetClientId;
|
public ulong TargetClientId;
|
||||||
|
|
||||||
|
public int NetworkSceneHandle;
|
||||||
|
|
||||||
public unsafe void Serialize(FastBufferWriter writer)
|
public unsafe void Serialize(FastBufferWriter writer)
|
||||||
{
|
{
|
||||||
if (!writer.TryBeginWrite(
|
var writeSize = sizeof(HeaderData);
|
||||||
sizeof(HeaderData) +
|
writeSize += Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
writeSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||||
(Header.IsReparented
|
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||||
? FastBufferWriter.GetWriteSize(IsLatestParentSet) +
|
|
||||||
(IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0)
|
if (!writer.TryBeginWrite(writeSize))
|
||||||
: 0)))
|
|
||||||
{
|
{
|
||||||
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
|
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
|
||||||
}
|
}
|
||||||
@@ -897,6 +951,16 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||||
|
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||||
|
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||||
|
// This is only set on in-scene placed NetworkObjects to reduce the over-all packet
|
||||||
|
// sizes for dynamically spawned NetworkObjects.
|
||||||
|
if (Header.IsSceneObject)
|
||||||
|
{
|
||||||
|
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
|
||||||
|
}
|
||||||
|
|
||||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -907,10 +971,12 @@ namespace Unity.Netcode
|
|||||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||||
}
|
}
|
||||||
reader.ReadValue(out Header);
|
reader.ReadValue(out Header);
|
||||||
if (!reader.TryBeginRead(
|
var readSize = Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
readSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||||
(Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) : 0)))
|
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||||
|
|
||||||
|
if (!reader.TryBeginRead(readSize))
|
||||||
{
|
{
|
||||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||||
}
|
}
|
||||||
@@ -934,6 +1000,16 @@ namespace Unity.Netcode
|
|||||||
LatestParent = latestParent;
|
LatestParent = latestParent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||||
|
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||||
|
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||||
|
// Client-side NetworkSceneManagers use this to locate their local instance of the
|
||||||
|
// NetworkObject instance.
|
||||||
|
if (Header.IsSceneObject)
|
||||||
|
{
|
||||||
|
reader.ReadValueSafe(out NetworkSceneHandle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1003,6 +1079,7 @@ namespace Unity.Netcode
|
|||||||
Vector3? position = null;
|
Vector3? position = null;
|
||||||
Quaternion? rotation = null;
|
Quaternion? rotation = null;
|
||||||
ulong? parentNetworkId = null;
|
ulong? parentNetworkId = null;
|
||||||
|
int? networkSceneHandle = null;
|
||||||
|
|
||||||
if (sceneObject.Header.HasTransform)
|
if (sceneObject.Header.HasTransform)
|
||||||
{
|
{
|
||||||
@@ -1015,10 +1092,15 @@ namespace Unity.Netcode
|
|||||||
parentNetworkId = sceneObject.ParentObjectId;
|
parentNetworkId = sceneObject.ParentObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sceneObject.Header.IsSceneObject)
|
||||||
|
{
|
||||||
|
networkSceneHandle = sceneObject.NetworkSceneHandle;
|
||||||
|
}
|
||||||
|
|
||||||
//Attempt to create a local NetworkObject
|
//Attempt to create a local NetworkObject
|
||||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(
|
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(
|
||||||
sceneObject.Header.IsSceneObject, sceneObject.Header.Hash,
|
sceneObject.Header.IsSceneObject, sceneObject.Header.Hash,
|
||||||
sceneObject.Header.OwnerClientId, parentNetworkId, position, rotation, sceneObject.Header.IsReparented);
|
sceneObject.Header.OwnerClientId, parentNetworkId, networkSceneHandle, position, rotation, sceneObject.Header.IsReparented);
|
||||||
|
|
||||||
networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent);
|
networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Header placed at the start of each message batch
|
/// Header placed at the start of each message batch
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal struct BatchHeader
|
internal struct BatchHeader : INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total number of messages in the batch.
|
/// Total number of messages in the batch.
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ namespace Unity.Netcode
|
|||||||
/// Sends the named message
|
/// Sends the named message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageName">The message name to send</param>
|
/// <param name="messageName">The message name to send</param>
|
||||||
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
|
/// <param name="clientIds">The clients to send to</param>
|
||||||
/// <param name="messageStream">The message stream containing the data</param>
|
/// <param name="messageStream">The message stream containing the data</param>
|
||||||
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
||||||
public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
||||||
|
|||||||
149
Runtime/Messaging/DeferredMessageManager.cs
Normal file
149
Runtime/Messaging/DeferredMessageManager.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Time = UnityEngine.Time;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class DeferredMessageManager : IDeferredMessageManager
|
||||||
|
{
|
||||||
|
protected struct TriggerData
|
||||||
|
{
|
||||||
|
public FastBufferReader Reader;
|
||||||
|
public MessageHeader Header;
|
||||||
|
public ulong SenderId;
|
||||||
|
public float Timestamp;
|
||||||
|
public int SerializedHeaderSize;
|
||||||
|
}
|
||||||
|
protected struct TriggerInfo
|
||||||
|
{
|
||||||
|
public float Expiry;
|
||||||
|
public NativeList<TriggerData> TriggerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Dictionary<IDeferredMessageManager.TriggerType, Dictionary<ulong, TriggerInfo>> m_Triggers = new Dictionary<IDeferredMessageManager.TriggerType, Dictionary<ulong, TriggerInfo>>();
|
||||||
|
|
||||||
|
private readonly NetworkManager m_NetworkManager;
|
||||||
|
|
||||||
|
internal DeferredMessageManager(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
m_NetworkManager = networkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||||
|
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||||
|
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
||||||
|
///
|
||||||
|
/// 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>
|
||||||
|
public virtual unsafe void DeferMessage(IDeferredMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context)
|
||||||
|
{
|
||||||
|
if (!m_Triggers.TryGetValue(trigger, out var triggers))
|
||||||
|
{
|
||||||
|
triggers = new Dictionary<ulong, TriggerInfo>();
|
||||||
|
m_Triggers[trigger] = triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!triggers.TryGetValue(key, out var triggerInfo))
|
||||||
|
{
|
||||||
|
triggerInfo = new TriggerInfo
|
||||||
|
{
|
||||||
|
Expiry = Time.realtimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout,
|
||||||
|
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
||||||
|
};
|
||||||
|
triggers[key] = triggerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerInfo.TriggerData.Add(new TriggerData
|
||||||
|
{
|
||||||
|
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
|
||||||
|
Header = context.Header,
|
||||||
|
Timestamp = context.Timestamp,
|
||||||
|
SenderId = context.SenderId,
|
||||||
|
SerializedHeaderSize = context.SerializedHeaderSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public virtual unsafe void CleanupStaleTriggers()
|
||||||
|
{
|
||||||
|
foreach (var kvp in m_Triggers)
|
||||||
|
{
|
||||||
|
ulong* staleKeys = stackalloc ulong[kvp.Value.Count];
|
||||||
|
int index = 0;
|
||||||
|
foreach (var kvp2 in kvp.Value)
|
||||||
|
{
|
||||||
|
if (kvp2.Value.Expiry < Time.realtimeSinceStartup)
|
||||||
|
{
|
||||||
|
staleKeys[index++] = kvp2.Key;
|
||||||
|
PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < index; ++i)
|
||||||
|
{
|
||||||
|
kvp.Value.Remove(staleKeys[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void PurgeTrigger(IDeferredMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo)
|
||||||
|
{
|
||||||
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"Deferred messages were received for a trigger of type {triggerType} with key {key}, but that trigger was not received within within {m_NetworkManager.NetworkConfig.SpawnTimeout} second(s).");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var data in triggerInfo.TriggerData)
|
||||||
|
{
|
||||||
|
data.Reader.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerInfo.TriggerData.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ProcessTriggers(IDeferredMessageManager.TriggerType trigger, ulong key)
|
||||||
|
{
|
||||||
|
if (m_Triggers.TryGetValue(trigger, out var triggers))
|
||||||
|
{
|
||||||
|
// This must happen after InvokeBehaviourNetworkSpawn, otherwise ClientRPCs and other messages can be
|
||||||
|
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
||||||
|
if (triggers.TryGetValue(key, out var triggerInfo))
|
||||||
|
{
|
||||||
|
foreach (var deferredMessage in triggerInfo.TriggerData)
|
||||||
|
{
|
||||||
|
// Reader will be disposed within HandleMessage
|
||||||
|
m_NetworkManager.MessagingSystem.HandleMessage(deferredMessage.Header, deferredMessage.Reader, deferredMessage.SenderId, deferredMessage.Timestamp, deferredMessage.SerializedHeaderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerInfo.TriggerData.Dispose();
|
||||||
|
triggers.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public virtual void CleanupAllTriggers()
|
||||||
|
{
|
||||||
|
foreach (var kvp in m_Triggers)
|
||||||
|
{
|
||||||
|
foreach (var kvp2 in kvp.Value)
|
||||||
|
{
|
||||||
|
foreach (var data in kvp2.Value.TriggerData)
|
||||||
|
{
|
||||||
|
data.Reader.Dispose();
|
||||||
|
}
|
||||||
|
kvp2.Value.TriggerData.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_Triggers.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/DeferredMessageManager.cs.meta
Normal file
3
Runtime/Messaging/DeferredMessageManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ac7f57f7d16a46e2aba65558e873727f
|
||||||
|
timeCreated: 1649799187
|
||||||
35
Runtime/Messaging/IDeferredMessageManager.cs
Normal file
35
Runtime/Messaging/IDeferredMessageManager.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal interface IDeferredMessageManager
|
||||||
|
{
|
||||||
|
internal enum TriggerType
|
||||||
|
{
|
||||||
|
OnSpawn,
|
||||||
|
OnAddPrefab,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||||
|
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||||
|
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
||||||
|
///
|
||||||
|
/// 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>
|
||||||
|
void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
void CleanupStaleTriggers();
|
||||||
|
|
||||||
|
void ProcessTriggers(TriggerType trigger, ulong key);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
void CleanupAllTriggers();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/IDeferredMessageManager.cs.meta
Normal file
3
Runtime/Messaging/IDeferredMessageManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7fb73a029c314763a04ebb015a07664d
|
||||||
|
timeCreated: 1649966331
|
||||||
@@ -91,8 +91,10 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="senderId">The source clientId</param>
|
/// <param name="senderId">The source clientId</param>
|
||||||
/// <param name="messageType">The type of the message</param>
|
/// <param name="messageType">The type of the message</param>
|
||||||
|
/// <param name="messageContent">The FastBufferReader containing the message</param>
|
||||||
|
/// <param name="context">The NetworkContext the message is being processed in</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool OnVerifyCanReceive(ulong senderId, Type messageType);
|
bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called after a message is serialized, but before it's handled.
|
/// Called after a message is serialized, but before it's handled.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is the header data that's serialized to the network when sending an <see cref="INetworkMessage"/>
|
/// This is the header data that's serialized to the network when sending an <see cref="INetworkMessage"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal struct MessageHeader
|
internal struct MessageHeader : INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The byte representation of the message type. This is automatically assigned to each message
|
/// The byte representation of the message type. This is automatically assigned to each message
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
internal struct ChangeOwnershipMessage : INetworkMessage
|
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public ulong NetworkObjectId;
|
public ulong NetworkObjectId;
|
||||||
public ulong OwnerClientId;
|
public ulong OwnerClientId;
|
||||||
@@ -20,7 +20,7 @@ namespace Unity.Netcode
|
|||||||
reader.ReadValueSafe(out this);
|
reader.ReadValueSafe(out this);
|
||||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||||
{
|
{
|
||||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context);
|
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,16 +101,24 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
// Note: Delegate creation allocates.
|
// Note: Delegate creation allocates.
|
||||||
// Note: ToArray() also allocates. :(
|
// Note: ToArray() also allocates. :(
|
||||||
networkManager.InvokeConnectionApproval(ConnectionData, senderId,
|
var response = new NetworkManager.ConnectionApprovalResponse();
|
||||||
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
|
networkManager.ClientsToApprove[senderId] = response;
|
||||||
|
|
||||||
|
networkManager.ConnectionApprovalCallback(
|
||||||
|
new NetworkManager.ConnectionApprovalRequest
|
||||||
{
|
{
|
||||||
var localCreatePlayerObject = createPlayerObject;
|
Payload = ConnectionData,
|
||||||
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, position, rotation);
|
ClientNetworkId = senderId
|
||||||
});
|
}, response);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
networkManager.HandleApproval(senderId, networkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null);
|
var response = new NetworkManager.ConnectionApprovalResponse
|
||||||
|
{
|
||||||
|
Approved = true,
|
||||||
|
CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null
|
||||||
|
};
|
||||||
|
networkManager.HandleConnectionApproval(senderId, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
ObjectInfo.Deserialize(reader);
|
ObjectInfo.Deserialize(reader);
|
||||||
|
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||||
|
{
|
||||||
|
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
m_ReceivedNetworkVariableData = reader;
|
m_ReceivedNetworkVariableData = reader;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
internal struct DestroyObjectMessage : INetworkMessage
|
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public ulong NetworkObjectId;
|
public ulong NetworkObjectId;
|
||||||
|
public bool DestroyGameObject;
|
||||||
|
|
||||||
public void Serialize(FastBufferWriter writer)
|
public void Serialize(FastBufferWriter writer)
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,14 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.ReadValueSafe(out this);
|
reader.ReadValueSafe(out this);
|
||||||
|
|
||||||
|
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||||
|
{
|
||||||
|
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +38,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
||||||
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
|
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ namespace Unity.Netcode
|
|||||||
networkVariable.CanClientRead(TargetClientId) &&
|
networkVariable.CanClientRead(TargetClientId) &&
|
||||||
(NetworkBehaviour.NetworkManager.IsServer || networkVariable.CanClientWrite(NetworkBehaviour.NetworkManager.LocalClientId));
|
(NetworkBehaviour.NetworkManager.IsServer || networkVariable.CanClientWrite(NetworkBehaviour.NetworkManager.LocalClientId));
|
||||||
|
|
||||||
|
// Prevent the server from writing to the client that owns a given NetworkVariable
|
||||||
|
// Allowing the write would send an old value to the client and cause jitter
|
||||||
|
if (networkVariable.WritePerm == NetworkVariableWritePermission.Owner &&
|
||||||
|
networkVariable.OwnerClientId() == TargetClientId)
|
||||||
|
{
|
||||||
|
shouldWrite = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||||
{
|
{
|
||||||
if (!shouldWrite)
|
if (!shouldWrite)
|
||||||
@@ -225,7 +233,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
|
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||||
{
|
{
|
||||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context);
|
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace Unity.Netcode
|
|||||||
var networkManager = (NetworkManager)context.SystemOwner;
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
|
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
|
||||||
{
|
{
|
||||||
networkManager.SpawnManager.TriggerOnSpawn(metadata.NetworkObjectId, reader, ref context);
|
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct RpcMetadata
|
internal struct RpcMetadata : INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public ulong NetworkObjectId;
|
public ulong NetworkObjectId;
|
||||||
public ushort NetworkBehaviourId;
|
public ushort NetworkBehaviourId;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
internal struct TimeSyncMessage : INetworkMessage
|
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public int Tick;
|
public int Tick;
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ namespace Unity.Netcode
|
|||||||
m_Hooks.Add(hooks);
|
m_Hooks.Add(hooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Unhook(INetworkHooks hooks)
|
||||||
|
{
|
||||||
|
m_Hooks.Remove(hooks);
|
||||||
|
}
|
||||||
|
|
||||||
private void RegisterMessageType(MessageWithHandler messageWithHandler)
|
private void RegisterMessageType(MessageWithHandler messageWithHandler)
|
||||||
{
|
{
|
||||||
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
|
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
|
||||||
@@ -208,11 +213,11 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanReceive(ulong clientId, Type messageType)
|
private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||||
{
|
{
|
||||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||||
{
|
{
|
||||||
if (!m_Hooks[hookIdx].OnVerifyCanReceive(clientId, messageType))
|
if (!m_Hooks[hookIdx].OnVerifyCanReceive(clientId, messageType, messageContent, ref context))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -240,7 +245,7 @@ namespace Unity.Netcode
|
|||||||
};
|
};
|
||||||
|
|
||||||
var type = m_ReverseTypeMap[header.MessageType];
|
var type = m_ReverseTypeMap[header.MessageType];
|
||||||
if (!CanReceive(senderId, type))
|
if (!CanReceive(senderId, type, reader, ref context))
|
||||||
{
|
{
|
||||||
reader.Dispose();
|
reader.Dispose();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Unity.Netcode
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Unity.Netcode
|
|||||||
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
||||||
{
|
{
|
||||||
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
||||||
|
private NativeList<T> m_ListAtLastReset = new NativeList<T>(64, Allocator.Persistent);
|
||||||
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
|
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,7 +42,11 @@ namespace Unity.Netcode
|
|||||||
public override void ResetDirty()
|
public override void ResetDirty()
|
||||||
{
|
{
|
||||||
base.ResetDirty();
|
base.ResetDirty();
|
||||||
m_DirtyEvents.Clear();
|
if (m_DirtyEvents.Length > 0)
|
||||||
|
{
|
||||||
|
m_DirtyEvents.Clear();
|
||||||
|
m_ListAtLastReset.CopyFrom(m_List);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -67,34 +72,35 @@ namespace Unity.Netcode
|
|||||||
writer.WriteValueSafe((ushort)m_DirtyEvents.Length);
|
writer.WriteValueSafe((ushort)m_DirtyEvents.Length);
|
||||||
for (int i = 0; i < m_DirtyEvents.Length; i++)
|
for (int i = 0; i < m_DirtyEvents.Length; i++)
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe(m_DirtyEvents[i].Type);
|
var element = m_DirtyEvents.ElementAt(i);
|
||||||
switch (m_DirtyEvents[i].Type)
|
writer.WriteValueSafe(element.Type);
|
||||||
|
switch (element.Type)
|
||||||
{
|
{
|
||||||
case NetworkListEvent<T>.EventType.Add:
|
case NetworkListEvent<T>.EventType.Add:
|
||||||
{
|
{
|
||||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NetworkListEvent<T>.EventType.Insert:
|
case NetworkListEvent<T>.EventType.Insert:
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
writer.WriteValueSafe(element.Index);
|
||||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NetworkListEvent<T>.EventType.Remove:
|
case NetworkListEvent<T>.EventType.Remove:
|
||||||
{
|
{
|
||||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NetworkListEvent<T>.EventType.RemoveAt:
|
case NetworkListEvent<T>.EventType.RemoveAt:
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
writer.WriteValueSafe(element.Index);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NetworkListEvent<T>.EventType.Value:
|
case NetworkListEvent<T>.EventType.Value:
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
writer.WriteValueSafe(element.Index);
|
||||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NetworkListEvent<T>.EventType.Clear:
|
case NetworkListEvent<T>.EventType.Clear:
|
||||||
@@ -109,10 +115,10 @@ namespace Unity.Netcode
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void WriteField(FastBufferWriter writer)
|
public override void WriteField(FastBufferWriter writer)
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe((ushort)m_List.Length);
|
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
|
||||||
for (int i = 0; i < m_List.Length; i++)
|
for (int i = 0; i < m_ListAtLastReset.Length; i++)
|
||||||
{
|
{
|
||||||
NetworkVariable<T>.Write(writer, m_List[i]);
|
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +129,7 @@ namespace Unity.Netcode
|
|||||||
reader.ReadValueSafe(out ushort count);
|
reader.ReadValueSafe(out ushort count);
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
NetworkVariable<T>.Read(reader, out T value);
|
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||||
m_List.Add(value);
|
m_List.Add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +145,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
case NetworkListEvent<T>.EventType.Add:
|
case NetworkListEvent<T>.EventType.Add:
|
||||||
{
|
{
|
||||||
NetworkVariable<T>.Read(reader, out T value);
|
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||||
m_List.Add(value);
|
m_List.Add(value);
|
||||||
|
|
||||||
if (OnListChanged != null)
|
if (OnListChanged != null)
|
||||||
@@ -166,7 +172,7 @@ namespace Unity.Netcode
|
|||||||
case NetworkListEvent<T>.EventType.Insert:
|
case NetworkListEvent<T>.EventType.Insert:
|
||||||
{
|
{
|
||||||
reader.ReadValueSafe(out int index);
|
reader.ReadValueSafe(out int index);
|
||||||
NetworkVariable<T>.Read(reader, out T value);
|
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||||
m_List[index] = value;
|
m_List[index] = value;
|
||||||
|
|
||||||
@@ -193,7 +199,7 @@ namespace Unity.Netcode
|
|||||||
break;
|
break;
|
||||||
case NetworkListEvent<T>.EventType.Remove:
|
case NetworkListEvent<T>.EventType.Remove:
|
||||||
{
|
{
|
||||||
NetworkVariable<T>.Read(reader, out T value);
|
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||||
int index = m_List.IndexOf(value);
|
int index = m_List.IndexOf(value);
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
@@ -253,7 +259,7 @@ namespace Unity.Netcode
|
|||||||
case NetworkListEvent<T>.EventType.Value:
|
case NetworkListEvent<T>.EventType.Value:
|
||||||
{
|
{
|
||||||
reader.ReadValueSafe(out int index);
|
reader.ReadValueSafe(out int index);
|
||||||
NetworkVariable<T>.Read(reader, out T value);
|
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||||
if (index >= m_List.Length)
|
if (index >= m_List.Length)
|
||||||
{
|
{
|
||||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||||
@@ -454,6 +460,7 @@ namespace Unity.Netcode
|
|||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
m_List.Dispose();
|
m_List.Dispose();
|
||||||
|
m_ListAtLastReset.Dispose();
|
||||||
m_DirtyEvents.Dispose();
|
m_DirtyEvents.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,53 +11,6 @@ namespace Unity.Netcode
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
|
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>
|
/// <summary>
|
||||||
/// Delegate type for value changed event
|
/// Delegate type for value changed event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -143,8 +96,13 @@ namespace Unity.Netcode
|
|||||||
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
|
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
|
||||||
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
||||||
{
|
{
|
||||||
|
// todo:
|
||||||
|
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
|
||||||
|
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
|
||||||
|
// would be stored in different fields
|
||||||
|
|
||||||
T previousValue = m_InternalValue;
|
T previousValue = m_InternalValue;
|
||||||
Read(reader, out m_InternalValue);
|
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||||
|
|
||||||
if (keepDirtyDelta)
|
if (keepDirtyDelta)
|
||||||
{
|
{
|
||||||
@@ -157,13 +115,13 @@ namespace Unity.Netcode
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void ReadField(FastBufferReader reader)
|
public override void ReadField(FastBufferReader reader)
|
||||||
{
|
{
|
||||||
Read(reader, out m_InternalValue);
|
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void WriteField(FastBufferWriter writer)
|
public override void WriteField(FastBufferWriter writer)
|
||||||
{
|
{
|
||||||
Write(writer, m_InternalValue);
|
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,14 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the ClientId of the owning client
|
||||||
|
/// </summary>
|
||||||
|
internal ulong OwnerClientId()
|
||||||
|
{
|
||||||
|
return m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the dirty changes, that is, the changes since the variable was last dirty, to the writer
|
/// Writes the dirty changes, that is, the changes since the variable was last dirty, to the writer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
248
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
248
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface used by NetworkVariables to serialize them
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
internal interface INetworkVariableSerializer<T>
|
||||||
|
{
|
||||||
|
// Write has to be taken by ref here because of INetworkSerializable
|
||||||
|
// Open Instance Delegates (pointers to methods without an instance attached to them)
|
||||||
|
// require the first parameter passed to them (the instance) to be passed by ref.
|
||||||
|
// So foo.Bar() becomes BarDelegate(ref foo);
|
||||||
|
// Taking T as an in parameter like we do in other places would require making a copy
|
||||||
|
// of it to pass it as a ref parameter.
|
||||||
|
public void Write(FastBufferWriter writer, ref T value);
|
||||||
|
public void Read(FastBufferReader reader, out T value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Basic serializer for unmanaged types.
|
||||||
|
/// This covers primitives, built-in unity types, and IForceSerializeByMemcpy
|
||||||
|
/// Since all of those ultimately end up calling WriteUnmanagedSafe, this simplifies things
|
||||||
|
/// by calling that directly - thus preventing us from having to have a specific T that meets
|
||||||
|
/// the specific constraints that the various generic WriteValue calls require.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
internal class UnmanagedTypeSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
public void Write(FastBufferWriter writer, ref T value)
|
||||||
|
{
|
||||||
|
writer.WriteUnmanagedSafe(value);
|
||||||
|
}
|
||||||
|
public void Read(FastBufferReader reader, out T value)
|
||||||
|
{
|
||||||
|
reader.ReadUnmanagedSafe(out value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializer for FixedStrings, which does the same thing FastBufferWriter/FastBufferReader do,
|
||||||
|
/// but is implemented to get the data it needs using open instance delegates that are passed in
|
||||||
|
/// via reflection. This prevents needing T to meet any interface requirements (which isn't achievable
|
||||||
|
/// without incurring GC allocs on every call to Write or Read - reflection + Open Instance Delegates
|
||||||
|
/// circumvent that.)
|
||||||
|
///
|
||||||
|
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||||
|
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||||
|
/// aren't known to actually contain those methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
internal delegate int GetLengthDelegate(ref T value);
|
||||||
|
internal delegate void SetLengthDelegate(ref T value, int length);
|
||||||
|
internal unsafe delegate byte* GetUnsafePtrDelegate(ref T value);
|
||||||
|
|
||||||
|
internal GetLengthDelegate GetLength;
|
||||||
|
internal SetLengthDelegate SetLength;
|
||||||
|
internal GetUnsafePtrDelegate GetUnsafePtr;
|
||||||
|
|
||||||
|
public unsafe void Write(FastBufferWriter writer, ref T value)
|
||||||
|
{
|
||||||
|
int length = GetLength(ref value);
|
||||||
|
byte* data = GetUnsafePtr(ref value);
|
||||||
|
writer.WriteUnmanagedSafe(length);
|
||||||
|
writer.WriteBytesSafe(data, length);
|
||||||
|
}
|
||||||
|
public unsafe void Read(FastBufferReader reader, out T value)
|
||||||
|
{
|
||||||
|
value = new T();
|
||||||
|
reader.ReadValueSafe(out int length);
|
||||||
|
SetLength(ref value, length);
|
||||||
|
reader.ReadBytesSafe(GetUnsafePtr(ref value), length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializer for INetworkSerializable types, which does the same thing
|
||||||
|
/// FastBufferWriter/FastBufferReader do, but is implemented to call the NetworkSerialize() method
|
||||||
|
/// via open instance delegates passed in via reflection. This prevents needing T to meet any interface
|
||||||
|
/// requirements (which isn't achievable without incurring GC allocs on every call to Write or Read -
|
||||||
|
/// reflection + Open Instance Delegates circumvent that.)
|
||||||
|
///
|
||||||
|
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||||
|
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||||
|
/// aren't known to actually contain those methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
internal class NetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
internal delegate void WriteValueDelegate(ref T value, BufferSerializer<BufferSerializerWriter> serializer);
|
||||||
|
internal delegate void ReadValueDelegate(ref T value, BufferSerializer<BufferSerializerReader> serializer);
|
||||||
|
|
||||||
|
internal WriteValueDelegate WriteValue;
|
||||||
|
internal ReadValueDelegate ReadValue;
|
||||||
|
public void Write(FastBufferWriter writer, ref T value)
|
||||||
|
{
|
||||||
|
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||||
|
WriteValue(ref value, bufferSerializer);
|
||||||
|
}
|
||||||
|
public void Read(FastBufferReader reader, out T value)
|
||||||
|
{
|
||||||
|
value = new T();
|
||||||
|
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||||
|
ReadValue(ref value, bufferSerializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is used to register user serialization with NetworkVariables for types
|
||||||
|
/// that are serialized via user serialization, such as with FastBufferReader and FastBufferWriter
|
||||||
|
/// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows
|
||||||
|
/// users to tell NetworkVariable about those extension methods (or simply pass in a lambda)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class UserNetworkVariableSerialization<T>
|
||||||
|
{
|
||||||
|
public delegate void WriteValueDelegate(FastBufferWriter writer, in T value);
|
||||||
|
public delegate void ReadValueDelegate(FastBufferReader reader, out T value);
|
||||||
|
|
||||||
|
public static WriteValueDelegate WriteValue;
|
||||||
|
public static ReadValueDelegate ReadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is instantiated for types that we can't determine ahead of time are serializable - types
|
||||||
|
/// that don't meet any of the constraints for methods that are available on FastBufferReader and
|
||||||
|
/// FastBufferWriter. These types may or may not be serializable through extension methods. To ensure
|
||||||
|
/// the user has time to pass in the delegates to UserNetworkVariableSerialization, the existence
|
||||||
|
/// of user serialization isn't checked until it's used, so if no serialization is provided, this
|
||||||
|
/// will throw an exception when an object containing the relevant NetworkVariable is spawned.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
|
||||||
|
{
|
||||||
|
public void Write(FastBufferWriter writer, ref T value)
|
||||||
|
{
|
||||||
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
||||||
|
}
|
||||||
|
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
||||||
|
}
|
||||||
|
public void Read(FastBufferReader reader, out T value)
|
||||||
|
{
|
||||||
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
||||||
|
}
|
||||||
|
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class NetworkVariableSerializationTypes
|
||||||
|
{
|
||||||
|
internal static readonly HashSet<Type> BaseSupportedTypes = new HashSet<Type>
|
||||||
|
{
|
||||||
|
typeof(bool),
|
||||||
|
typeof(byte),
|
||||||
|
typeof(sbyte),
|
||||||
|
typeof(char),
|
||||||
|
typeof(decimal),
|
||||||
|
typeof(double),
|
||||||
|
typeof(float),
|
||||||
|
typeof(int),
|
||||||
|
typeof(uint),
|
||||||
|
typeof(long),
|
||||||
|
typeof(ulong),
|
||||||
|
typeof(short),
|
||||||
|
typeof(ushort),
|
||||||
|
typeof(Vector2),
|
||||||
|
typeof(Vector3),
|
||||||
|
typeof(Vector2Int),
|
||||||
|
typeof(Vector3Int),
|
||||||
|
typeof(Vector4),
|
||||||
|
typeof(Quaternion),
|
||||||
|
typeof(Color),
|
||||||
|
typeof(Color32),
|
||||||
|
typeof(Ray),
|
||||||
|
typeof(Ray2D)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Support methods for reading/writing NetworkVariables
|
||||||
|
/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints,
|
||||||
|
/// but there's no way to achieve the same thing with a class, this sets up various read/write schemes
|
||||||
|
/// based on which constraints are met by `T` using reflection, which is done at module load time.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public static class NetworkVariableSerialization<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
private static INetworkVariableSerializer<T> s_Serializer = GetSerializer();
|
||||||
|
|
||||||
|
private static INetworkVariableSerializer<T> GetSerializer()
|
||||||
|
{
|
||||||
|
if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T)))
|
||||||
|
{
|
||||||
|
return new UnmanagedTypeSerializer<T>();
|
||||||
|
}
|
||||||
|
if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
return new UnmanagedTypeSerializer<T>();
|
||||||
|
}
|
||||||
|
if (typeof(Enum).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
return new UnmanagedTypeSerializer<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
// Obtains "Open Instance Delegates" for the type's NetworkSerialize() methods -
|
||||||
|
// one for an instance of the generic method taking BufferSerializerWriter as T,
|
||||||
|
// one for an instance of the generic method taking BufferSerializerReader as T
|
||||||
|
var writeMethod = (NetworkSerializableSerializer<T>.WriteValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.WriteValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerWriter)));
|
||||||
|
var readMethod = (NetworkSerializableSerializer<T>.ReadValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.ReadValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerReader)));
|
||||||
|
return new NetworkSerializableSerializer<T> { WriteValue = writeMethod, ReadValue = readMethod };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(IUTF8Bytes).IsAssignableFrom(typeof(T)) && typeof(INativeList<byte>).IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
// Get "OpenInstanceDelegates" for the Length property (get and set, which are prefixed
|
||||||
|
// with "get_" and "set_" under the hood and emitted as methods) and GetUnsafePtr()
|
||||||
|
var getLength = (FixedStringSerializer<T>.GetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetLengthDelegate), null, typeof(T).GetMethod("get_" + nameof(INativeList<byte>.Length)));
|
||||||
|
var setLength = (FixedStringSerializer<T>.SetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.SetLengthDelegate), null, typeof(T).GetMethod("set_" + nameof(INativeList<byte>.Length)));
|
||||||
|
var getUnsafePtr = (FixedStringSerializer<T>.GetUnsafePtrDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetUnsafePtrDelegate), null, typeof(T).GetMethod(nameof(IUTF8Bytes.GetUnsafePtr)));
|
||||||
|
return new FixedStringSerializer<T> { GetLength = getLength, SetLength = setLength, GetUnsafePtr = getUnsafePtr };
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FallbackSerializer<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Write(FastBufferWriter writer, ref T value)
|
||||||
|
{
|
||||||
|
s_Serializer.Write(writer, ref value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Read(FastBufferReader reader, out T value)
|
||||||
|
{
|
||||||
|
s_Serializer.Read(reader, out value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2c6ef5fdf2e94ec3b4ce8086d52700b3
|
||||||
|
timeCreated: 1650985453
|
||||||
@@ -82,7 +82,7 @@ namespace Unity.Netcode
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,14 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
internal uint SceneEventId;
|
internal uint SceneEventId;
|
||||||
internal Action<uint> EventAction;
|
internal Action<uint> EventAction;
|
||||||
|
/// <summary>
|
||||||
|
/// Used server-side for integration testing in order to
|
||||||
|
/// invoke the SceneEventProgress once done loading
|
||||||
|
/// </summary>
|
||||||
|
internal Action Completed;
|
||||||
internal void Invoke()
|
internal void Invoke()
|
||||||
{
|
{
|
||||||
|
Completed?.Invoke();
|
||||||
EventAction.Invoke(SceneEventId);
|
EventAction.Invoke(SceneEventId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace Unity.Netcode
|
|||||||
/// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
|
/// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
|
||||||
/// scene event status.<br/>
|
/// scene event status.<br/>
|
||||||
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
|
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// See also: <br/>
|
/// See also: <br/>
|
||||||
/// <seealso cref="SceneEventType"/>
|
/// <seealso cref="SceneEventType"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -166,6 +167,7 @@ namespace Unity.Netcode
|
|||||||
/// <item><term><see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> 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>
|
/// <item><term><see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed</term></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
|
/// <em>Note: Do not start new scene events within NetworkSceneManager scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event SceneEventDelegate OnSceneEvent;
|
public event SceneEventDelegate OnSceneEvent;
|
||||||
|
|
||||||
@@ -239,13 +241,15 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server.<br/>
|
/// 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>
|
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnLoadDelegateHandler OnLoad;
|
public event OnLoadDelegateHandler OnLoad;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server.<br/>
|
/// 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>
|
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnUnloadDelegateHandler OnUnload;
|
public event OnUnloadDelegateHandler OnUnload;
|
||||||
|
|
||||||
@@ -254,7 +258,8 @@ namespace Unity.Netcode
|
|||||||
/// after a client is approved for connection in order to synchronize the client with the currently loaded
|
/// 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.<br/>
|
/// 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.
|
/// <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>
|
/// This event is generated on a per newly connected and approved client basis.</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnSynchronizeDelegateHandler OnSynchronize;
|
public event OnSynchronizeDelegateHandler OnSynchronize;
|
||||||
|
|
||||||
@@ -263,7 +268,8 @@ namespace Unity.Netcode
|
|||||||
/// This event signifies the end of an existing <see cref="SceneEventType.Load"/> event as it pertains
|
/// 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
|
/// 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.<br/>
|
/// 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>
|
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
|
public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
|
||||||
|
|
||||||
@@ -273,21 +279,24 @@ namespace Unity.Netcode
|
|||||||
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
|
/// 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.<br/>
|
/// 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
|
/// <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>
|
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
|
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.<br/>
|
/// 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).
|
/// <em>Note: The server receives this message from all clients (including itself).
|
||||||
/// Each client receives their own notification sent to the server.</em>
|
/// Each client receives their own notification sent to the server.</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnLoadCompleteDelegateHandler OnLoadComplete;
|
public event OnLoadCompleteDelegateHandler OnLoadComplete;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.<br/>
|
/// 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).
|
/// <em>Note: The server receives this message from all clients (including itself).
|
||||||
/// Each client receives their own notification sent to the server.</em>
|
/// Each client receives their own notification sent to the server.</em><br/>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
|
public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
|
||||||
|
|
||||||
@@ -296,6 +305,7 @@ namespace Unity.Netcode
|
|||||||
/// <em> Note: The server receives this message from the client, but will never generate this event for itself.
|
/// <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
|
/// 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>
|
/// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects.</em>
|
||||||
|
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
|
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
|
||||||
|
|
||||||
@@ -319,8 +329,12 @@ namespace Unity.Netcode
|
|||||||
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
|
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Proof of concept: Interface version that allows for the decoupling from
|
/// The SceneManagerHandler implementation
|
||||||
/// the SceneManager's Load and Unload methods for testing purposes (potentially other future usage)
|
/// </summary>
|
||||||
|
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private class DefaultSceneManagerHandler : ISceneManagerHandler
|
private class DefaultSceneManagerHandler : ISceneManagerHandler
|
||||||
{
|
{
|
||||||
@@ -339,10 +353,6 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
|
||||||
/// End of Proof of Concept
|
|
||||||
|
|
||||||
|
|
||||||
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
|
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -425,6 +435,8 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
SceneUnloadEventHandler.Shutdown();
|
||||||
|
|
||||||
foreach (var keypair in SceneEventDataStore)
|
foreach (var keypair in SceneEventDataStore)
|
||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
@@ -632,6 +644,12 @@ namespace Unity.Netcode
|
|||||||
return validated;
|
return validated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for NetcodeIntegrationTest testing in order to properly
|
||||||
|
/// assign the right loaded scene to the right client's ScenesLoaded list
|
||||||
|
/// </summary>
|
||||||
|
internal Func<string, Scene> OverrideGetAndAddNewlyLoadedSceneByName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Since SceneManager.GetSceneByName only returns the first scene that matches the name
|
/// Since SceneManager.GetSceneByName only returns the first scene that matches the name
|
||||||
/// we must "find" a newly added scene by looking through all loaded scenes and determining
|
/// we must "find" a newly added scene by looking through all loaded scenes and determining
|
||||||
@@ -643,20 +661,27 @@ namespace Unity.Netcode
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
|
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
if (OverrideGetAndAddNewlyLoadedSceneByName != null)
|
||||||
{
|
{
|
||||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
return OverrideGetAndAddNewlyLoadedSceneByName.Invoke(sceneName);
|
||||||
if (sceneLoaded.name == sceneName)
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
{
|
{
|
||||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||||
|
if (sceneLoaded.name == sceneName)
|
||||||
{
|
{
|
||||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||||
return sceneLoaded;
|
{
|
||||||
|
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||||
|
return sceneLoaded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -720,18 +745,18 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="globalObjectIdHash"></param>
|
/// <param name="globalObjectIdHash"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash)
|
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash, int? networkSceneHandle)
|
||||||
{
|
{
|
||||||
if (ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
if (ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||||
{
|
{
|
||||||
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(SceneBeingSynchronized.handle))
|
var sceneHandle = SceneBeingSynchronized.handle;
|
||||||
|
if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0)
|
||||||
{
|
{
|
||||||
var inScenePlacedNetworkObject = ScenePlacedObjects[globalObjectIdHash][SceneBeingSynchronized.handle];
|
sceneHandle = ServerSceneHandleToClientSceneHandle[networkSceneHandle.Value];
|
||||||
|
}
|
||||||
// We can only have 1 duplicated globalObjectIdHash per scene instance, so remove it once it has been returned
|
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
|
||||||
ScenePlacedObjects[globalObjectIdHash].Remove(SceneBeingSynchronized.handle);
|
{
|
||||||
|
return ScenePlacedObjects[globalObjectIdHash][sceneHandle];
|
||||||
return inScenePlacedNetworkObject;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -859,7 +884,6 @@ namespace Unity.Netcode
|
|||||||
/// Callback for the <see cref="SceneEventProgress.OnComplete"/> <see cref="SceneEventProgress.OnCompletedDelegate"/> handler
|
/// Callback for the <see cref="SceneEventProgress.OnComplete"/> <see cref="SceneEventProgress.OnCompletedDelegate"/> handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sceneEventProgress"></param>
|
/// <param name="sceneEventProgress"></param>
|
||||||
/// <returns></returns>
|
|
||||||
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
|
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
|
||||||
{
|
{
|
||||||
var sceneEventData = BeginSceneEvent();
|
var sceneEventData = BeginSceneEvent();
|
||||||
@@ -868,7 +892,7 @@ namespace Unity.Netcode
|
|||||||
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
|
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
|
||||||
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
|
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
|
||||||
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
|
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
|
||||||
sceneEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList();
|
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList();
|
||||||
|
|
||||||
var message = new SceneEventMessage
|
var message = new SceneEventMessage
|
||||||
{
|
{
|
||||||
@@ -945,11 +969,18 @@ namespace Unity.Netcode
|
|||||||
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
|
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
|
||||||
|
|
||||||
ScenesLoaded.Remove(scene.handle);
|
ScenesLoaded.Remove(scene.handle);
|
||||||
|
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded };
|
||||||
|
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventAction);
|
||||||
|
|
||||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene,
|
// If integration testing, IntegrationTestSceneHandler returns null
|
||||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
|
if (sceneUnload == null)
|
||||||
|
{
|
||||||
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify local server that a scene is going to be unloaded
|
// Notify local server that a scene is going to be unloaded
|
||||||
OnSceneEvent?.Invoke(new SceneEvent()
|
OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
@@ -1021,6 +1052,12 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnSceneUnloaded(uint sceneEventId)
|
private void OnSceneUnloaded(uint sceneEventId)
|
||||||
{
|
{
|
||||||
|
// If we are shutdown or about to shutdown, then ignore this event
|
||||||
|
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||||
// First thing we do, if we are a server, is to send the unload scene event.
|
// First thing we do, if we are a server, is to send the unload scene event.
|
||||||
if (m_NetworkManager.IsServer)
|
if (m_NetworkManager.IsServer)
|
||||||
@@ -1064,7 +1101,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
private void EmptySceneUnloadedOperation(uint sceneEventId)
|
private void EmptySceneUnloadedOperation(uint sceneEventId)
|
||||||
{
|
{
|
||||||
// Do nothing (this is a stub call since it is only used to flush all currently loaded scenes)
|
// Do nothing (this is a stub call since it is only used to flush all additively loaded scenes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1074,6 +1111,7 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void UnloadAdditivelyLoadedScenes(uint sceneEventId)
|
internal void UnloadAdditivelyLoadedScenes(uint sceneEventId)
|
||||||
{
|
{
|
||||||
|
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||||
// Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ).
|
// Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ).
|
||||||
var currentActiveScene = SceneManager.GetActiveScene();
|
var currentActiveScene = SceneManager.GetActiveScene();
|
||||||
foreach (var keyHandleEntry in ScenesLoaded)
|
foreach (var keyHandleEntry in ScenesLoaded)
|
||||||
@@ -1083,15 +1121,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
|
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
|
||||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
|
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
|
||||||
|
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
|
||||||
OnSceneEvent?.Invoke(new SceneEvent()
|
|
||||||
{
|
|
||||||
AsyncOperation = sceneUnload,
|
|
||||||
SceneEventType = SceneEventType.Unload,
|
|
||||||
SceneName = keyHandleEntry.Value.name,
|
|
||||||
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
|
|
||||||
ClientId = NetworkManager.ServerClientId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// clear out our scenes loaded list
|
// clear out our scenes loaded list
|
||||||
@@ -1124,12 +1154,12 @@ namespace Unity.Netcode
|
|||||||
sceneEventData.SceneEventType = SceneEventType.Load;
|
sceneEventData.SceneEventType = SceneEventType.Load;
|
||||||
sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName);
|
sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName);
|
||||||
sceneEventData.LoadSceneMode = loadSceneMode;
|
sceneEventData.LoadSceneMode = loadSceneMode;
|
||||||
|
var sceneEventId = sceneEventData.SceneEventId;
|
||||||
// This both checks to make sure the scene is valid and if not resets the active scene event
|
// This both checks to make sure the scene is valid and if not resets the active scene event
|
||||||
m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
|
m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
|
||||||
if (!m_IsSceneEventActive)
|
if (!m_IsSceneEventActive)
|
||||||
{
|
{
|
||||||
EndSceneEvent(sceneEventData.SceneEventId);
|
EndSceneEvent(sceneEventId);
|
||||||
return SceneEventProgressStatus.SceneFailedVerification;
|
return SceneEventProgressStatus.SceneFailedVerification;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1142,14 +1172,24 @@ namespace Unity.Netcode
|
|||||||
MoveObjectsToDontDestroyOnLoad();
|
MoveObjectsToDontDestroyOnLoad();
|
||||||
|
|
||||||
// Now Unload all currently additively loaded scenes
|
// Now Unload all currently additively loaded scenes
|
||||||
UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId);
|
UnloadAdditivelyLoadedScenes(sceneEventId);
|
||||||
|
|
||||||
|
// Register the active scene for unload scene event notifications
|
||||||
|
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now start loading the scene
|
// Now start loading the scene
|
||||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
|
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded };
|
||||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneLoaded });
|
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventAction);
|
||||||
|
// If integration testing, IntegrationTestSceneHandler returns null
|
||||||
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
if (sceneLoad == null)
|
||||||
|
{
|
||||||
|
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify the local server that a scene loading event has begun
|
// Notify the local server that a scene loading event has begun
|
||||||
OnSceneEvent?.Invoke(new SceneEvent()
|
OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
@@ -1167,6 +1207,114 @@ namespace Unity.Netcode
|
|||||||
return sceneEventProgress.Status;
|
return sceneEventProgress.Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class used to handle "odd ball" scene unload event notification scenarios
|
||||||
|
/// when scene switching.
|
||||||
|
/// </summary>
|
||||||
|
internal class SceneUnloadEventHandler
|
||||||
|
{
|
||||||
|
private static Dictionary<NetworkManager, List<SceneUnloadEventHandler>> s_Instances = new Dictionary<NetworkManager, List<SceneUnloadEventHandler>>();
|
||||||
|
|
||||||
|
internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||||
|
{
|
||||||
|
var networkManager = networkSceneManager.m_NetworkManager;
|
||||||
|
if (!s_Instances.ContainsKey(networkManager))
|
||||||
|
{
|
||||||
|
s_Instances.Add(networkManager, new List<SceneUnloadEventHandler>());
|
||||||
|
}
|
||||||
|
var clientId = networkManager.IsServer ? NetworkManager.ServerClientId : networkManager.LocalClientId;
|
||||||
|
s_Instances[networkManager].Add(new SceneUnloadEventHandler(networkSceneManager, scene, clientId, loadSceneMode, asyncOperation));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SceneUnloadComplete(SceneUnloadEventHandler sceneUnloadEventHandler)
|
||||||
|
{
|
||||||
|
if (sceneUnloadEventHandler == null || sceneUnloadEventHandler.m_NetworkSceneManager == null || sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var networkManager = sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager;
|
||||||
|
if (s_Instances.ContainsKey(networkManager))
|
||||||
|
{
|
||||||
|
s_Instances[networkManager].Remove(sceneUnloadEventHandler);
|
||||||
|
if (s_Instances[networkManager].Count == 0)
|
||||||
|
{
|
||||||
|
s_Instances.Remove(networkManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by NetworkSceneManager when it is disposing
|
||||||
|
/// </summary>
|
||||||
|
internal static void Shutdown()
|
||||||
|
{
|
||||||
|
foreach (var instanceEntry in s_Instances)
|
||||||
|
{
|
||||||
|
foreach (var instance in instanceEntry.Value)
|
||||||
|
{
|
||||||
|
instance.OnShutdown();
|
||||||
|
}
|
||||||
|
instanceEntry.Value.Clear();
|
||||||
|
}
|
||||||
|
s_Instances.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkSceneManager m_NetworkSceneManager;
|
||||||
|
private AsyncOperation m_AsyncOperation;
|
||||||
|
private LoadSceneMode m_LoadSceneMode;
|
||||||
|
private ulong m_ClientId;
|
||||||
|
private Scene m_Scene;
|
||||||
|
private bool m_ShuttingDown;
|
||||||
|
|
||||||
|
private void OnShutdown()
|
||||||
|
{
|
||||||
|
m_ShuttingDown = true;
|
||||||
|
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SceneUnloaded(Scene scene)
|
||||||
|
{
|
||||||
|
if (m_Scene.handle == scene.handle && !m_ShuttingDown)
|
||||||
|
{
|
||||||
|
if (m_NetworkSceneManager != null && m_NetworkSceneManager.m_NetworkManager != null)
|
||||||
|
{
|
||||||
|
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
|
{
|
||||||
|
AsyncOperation = m_AsyncOperation,
|
||||||
|
SceneEventType = SceneEventType.UnloadComplete,
|
||||||
|
SceneName = m_Scene.name,
|
||||||
|
LoadSceneMode = m_LoadSceneMode,
|
||||||
|
ClientId = m_ClientId
|
||||||
|
});
|
||||||
|
m_NetworkSceneManager.OnUnloadComplete?.Invoke(m_ClientId, m_Scene.name);
|
||||||
|
}
|
||||||
|
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||||
|
SceneUnloadComplete(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene scene, ulong clientId, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||||
|
{
|
||||||
|
m_LoadSceneMode = loadSceneMode;
|
||||||
|
m_AsyncOperation = asyncOperation;
|
||||||
|
m_NetworkSceneManager = networkSceneManager;
|
||||||
|
m_ClientId = clientId;
|
||||||
|
m_Scene = scene;
|
||||||
|
SceneManager.sceneUnloaded += SceneUnloaded;
|
||||||
|
// Send the initial unload event notification
|
||||||
|
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
|
{
|
||||||
|
AsyncOperation = m_AsyncOperation,
|
||||||
|
SceneEventType = SceneEventType.Unload,
|
||||||
|
SceneName = m_Scene.name,
|
||||||
|
LoadSceneMode = m_LoadSceneMode,
|
||||||
|
ClientId = clientId
|
||||||
|
});
|
||||||
|
|
||||||
|
m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.m_NetworkManager.LocalClientId, m_Scene.name, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client Side:
|
/// Client Side:
|
||||||
/// Handles both forms of scene loading
|
/// Handles both forms of scene loading
|
||||||
@@ -1201,6 +1349,10 @@ namespace Unity.Netcode
|
|||||||
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
|
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
|
||||||
{
|
{
|
||||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||||
|
|
||||||
|
// Register the active scene for unload scene event notifications
|
||||||
|
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
|
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
|
||||||
@@ -1218,13 +1370,18 @@ namespace Unity.Netcode
|
|||||||
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client and Server:
|
/// Client and Server:
|
||||||
/// Generic on scene loaded callback method to be called upon a scene loading
|
/// Generic on scene loaded callback method to be called upon a scene loading
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnSceneLoaded(uint sceneEventId)
|
private void OnSceneLoaded(uint sceneEventId)
|
||||||
{
|
{
|
||||||
|
// If we are shutdown or about to shutdown, then ignore this event
|
||||||
|
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||||
var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash));
|
var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash));
|
||||||
if (!nextScene.isLoaded || !nextScene.IsValid())
|
if (!nextScene.isLoaded || !nextScene.IsValid())
|
||||||
@@ -1363,6 +1520,13 @@ namespace Unity.Netcode
|
|||||||
EndSceneEvent(sceneEventId);
|
EndSceneEvent(sceneEventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for integration testing, due to the complexities of having all clients loading scenes
|
||||||
|
/// this is needed to "filter" out the scenes not loaded by NetworkSceneManager
|
||||||
|
/// (i.e. we don't want a late joining player to load all of the other client scenes)
|
||||||
|
/// </summary>
|
||||||
|
internal Func<Scene, bool> ExcludeSceneFromSychronization;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server Side:
|
/// Server Side:
|
||||||
/// This is used for players that have just had their connection approved and will assure they are synchronized
|
/// This is used for players that have just had their connection approved and will assure they are synchronized
|
||||||
@@ -1389,6 +1553,13 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
var scene = SceneManager.GetSceneAt(i);
|
var scene = SceneManager.GetSceneAt(i);
|
||||||
|
|
||||||
|
// NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager
|
||||||
|
// unless the scene in question is the currently active scene.
|
||||||
|
if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var sceneHash = SceneHashFromNameOrPath(scene.path);
|
var sceneHash = SceneHashFromNameOrPath(scene.path);
|
||||||
|
|
||||||
// This would depend upon whether we are additive or not
|
// This would depend upon whether we are additive or not
|
||||||
@@ -1406,11 +1577,11 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneEventData.AddSceneToSynchronize(sceneHash, scene.handle);
|
sceneEventData.AddSceneToSynchronize(sceneHash, scene.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneEventData.AddSpawnedNetworkObjects();
|
sceneEventData.AddSpawnedNetworkObjects();
|
||||||
|
sceneEventData.AddDespawnedInSceneNetworkObjects();
|
||||||
|
|
||||||
var message = new SceneEventMessage
|
var message = new SceneEventMessage
|
||||||
{
|
{
|
||||||
@@ -1446,12 +1617,9 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive;
|
var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive;
|
||||||
|
|
||||||
// Always check to see if the scene needs to be validated
|
// Store the sceneHandle and hash
|
||||||
if (!ValidateSceneBeforeLoading(sceneHash, loadSceneMode))
|
sceneEventData.NetworkSceneHandle = sceneHandle;
|
||||||
{
|
sceneEventData.ClientSceneHash = sceneHash;
|
||||||
EndSceneEvent(sceneEventId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the beginning of the synchronization event, then send client a notification that synchronization has begun
|
// If this is the beginning of the synchronization event, then send client a notification that synchronization has begun
|
||||||
if (sceneHash == sceneEventData.SceneHash)
|
if (sceneHash == sceneEventData.SceneHash)
|
||||||
@@ -1468,9 +1636,16 @@ namespace Unity.Netcode
|
|||||||
ScenePlacedObjects.Clear();
|
ScenePlacedObjects.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the sceneHandle and hash
|
// Always check to see if the scene needs to be validated
|
||||||
sceneEventData.ClientSceneHandle = sceneHandle;
|
if (!ValidateSceneBeforeLoading(sceneHash, loadSceneMode))
|
||||||
sceneEventData.ClientSceneHash = sceneHash;
|
{
|
||||||
|
HandleClientSceneEvent(sceneEventId);
|
||||||
|
if (m_NetworkManager.LogLevel == LogLevel.Developer)
|
||||||
|
{
|
||||||
|
NetworkLog.LogInfo($"Client declined to load the scene {sceneName}, continuing with synchronization.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var shouldPassThrough = false;
|
var shouldPassThrough = false;
|
||||||
var sceneLoad = (AsyncOperation)null;
|
var sceneLoad = (AsyncOperation)null;
|
||||||
@@ -1532,9 +1707,9 @@ namespace Unity.Netcode
|
|||||||
SceneManager.SetActiveScene(nextScene);
|
SceneManager.SetActiveScene(nextScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.ClientSceneHandle))
|
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.NetworkSceneHandle))
|
||||||
{
|
{
|
||||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.ClientSceneHandle, nextScene.handle);
|
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.NetworkSceneHandle, nextScene.handle);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1746,7 +1921,9 @@ namespace Unity.Netcode
|
|||||||
// NetworkObjects
|
// NetworkObjects
|
||||||
m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
|
m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
|
||||||
|
|
||||||
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization)
|
// Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid
|
||||||
|
// a potential crash within the MessageSystem (i.e. sending to a client that no longer exists)
|
||||||
|
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization && m_NetworkManager.ConnectedClients.ContainsKey(clientId))
|
||||||
{
|
{
|
||||||
sceneEventData.SceneEventType = SceneEventType.ReSynchronize;
|
sceneEventData.SceneEventType = SceneEventType.ReSynchronize;
|
||||||
SendSceneEventData(sceneEventId, new ulong[] { clientId });
|
SendSceneEventData(sceneEventId, new ulong[] { clientId });
|
||||||
@@ -1856,10 +2033,9 @@ namespace Unity.Netcode
|
|||||||
foreach (var networkObjectInstance in networkObjects)
|
foreach (var networkObjectInstance in networkObjects)
|
||||||
{
|
{
|
||||||
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
||||||
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
|
var sceneHandle = networkObjectInstance.GetSceneOriginHandle();
|
||||||
// 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 (for 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 (for additive scenes)
|
||||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
|
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && sceneHandle == sceneToFilterBy.handle)
|
||||||
sceneHandle == sceneToFilterBy.handle)
|
|
||||||
{
|
{
|
||||||
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
internal SceneEventType SceneEventType;
|
internal SceneEventType SceneEventType;
|
||||||
internal LoadSceneMode LoadSceneMode;
|
internal LoadSceneMode LoadSceneMode;
|
||||||
internal Guid SceneEventProgressId;
|
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
|
||||||
internal uint SceneEventId;
|
internal uint SceneEventId;
|
||||||
|
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
// Used by the client during synchronization
|
// Used by the client during synchronization
|
||||||
internal uint ClientSceneHash;
|
internal uint ClientSceneHash;
|
||||||
internal int ClientSceneHandle;
|
internal int NetworkSceneHandle;
|
||||||
|
|
||||||
/// Only used for <see cref="SceneEventType.Synchronize"/> scene events, this assures permissions when writing
|
/// 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
|
/// NetworkVariable information. If that process changes, then we need to update this
|
||||||
@@ -118,6 +118,9 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private List<NetworkObject> m_NetworkObjectsSync = new List<NetworkObject>();
|
private List<NetworkObject> m_NetworkObjectsSync = new List<NetworkObject>();
|
||||||
|
|
||||||
|
private List<NetworkObject> m_DespawnedInSceneObjectsSync = new List<NetworkObject>();
|
||||||
|
private Dictionary<int, List<uint>> m_DespawnedInSceneObjects = new Dictionary<int, List<uint>>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server Side Re-Synchronization:
|
/// Server Side Re-Synchronization:
|
||||||
/// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned,
|
/// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned,
|
||||||
@@ -243,6 +246,19 @@ namespace Unity.Netcode
|
|||||||
m_NetworkObjectsSync.Sort(SortNetworkObjects);
|
m_NetworkObjectsSync.Sort(SortNetworkObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void AddDespawnedInSceneNetworkObjects()
|
||||||
|
{
|
||||||
|
m_DespawnedInSceneObjectsSync.Clear();
|
||||||
|
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager);
|
||||||
|
foreach (var sobj in inSceneNetworkObjects)
|
||||||
|
{
|
||||||
|
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
|
||||||
|
{
|
||||||
|
m_DespawnedInSceneObjectsSync.Add(sobj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server Side:
|
/// Server Side:
|
||||||
/// Used during the synchronization process to associate NetworkObjects with scenes
|
/// Used during the synchronization process to associate NetworkObjects with scenes
|
||||||
@@ -372,7 +388,6 @@ namespace Unity.Netcode
|
|||||||
writer.WriteValueSafe(ScenesToSynchronize.ToArray());
|
writer.WriteValueSafe(ScenesToSynchronize.ToArray());
|
||||||
writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray());
|
writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray());
|
||||||
|
|
||||||
|
|
||||||
// Store our current position in the stream to come back and say how much data we have written
|
// Store our current position in the stream to come back and say how much data we have written
|
||||||
var positionStart = writer.Position;
|
var positionStart = writer.Position;
|
||||||
|
|
||||||
@@ -383,17 +398,31 @@ namespace Unity.Netcode
|
|||||||
int totalBytes = 0;
|
int totalBytes = 0;
|
||||||
|
|
||||||
// Write the number of NetworkObjects we are serializing
|
// Write the number of NetworkObjects we are serializing
|
||||||
writer.WriteValueSafe(m_NetworkObjectsSync.Count());
|
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count());
|
||||||
|
// Serialize all NetworkObjects that are spawned
|
||||||
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
|
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
|
||||||
{
|
{
|
||||||
var noStart = writer.Position;
|
var noStart = writer.Position;
|
||||||
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||||
writer.WriteValueSafe(m_NetworkObjectsSync[i].gameObject.scene.handle);
|
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
|
||||||
sceneObject.Serialize(writer);
|
sceneObject.Serialize(writer);
|
||||||
var noStop = writer.Position;
|
var noStop = writer.Position;
|
||||||
totalBytes += (int)(noStop - noStart);
|
totalBytes += (int)(noStop - noStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write the number of despawned in-scene placed NetworkObjects
|
||||||
|
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count());
|
||||||
|
// Write the scene handle and GlobalObjectIdHash value
|
||||||
|
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i)
|
||||||
|
{
|
||||||
|
var noStart = writer.Position;
|
||||||
|
var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||||
|
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||||
|
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||||
|
var noStop = writer.Position;
|
||||||
|
totalBytes += (int)(noStop - noStart);
|
||||||
|
}
|
||||||
|
|
||||||
// Size Place Holder -- End
|
// Size Place Holder -- End
|
||||||
var positionEnd = writer.Position;
|
var positionEnd = writer.Position;
|
||||||
var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint)));
|
var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint)));
|
||||||
@@ -683,15 +712,16 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Process all NetworkObjects for this scene
|
// Process all spawned NetworkObjects for this network session
|
||||||
InternalBuffer.ReadValueSafe(out int newObjectsCount);
|
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < newObjectsCount; i++)
|
for (int i = 0; i < newObjectsCount; i++)
|
||||||
{
|
{
|
||||||
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
|
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
|
||||||
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
|
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
|
||||||
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
|
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
|
||||||
InternalBuffer.ReadValueSafe(out int handle);
|
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
|
||||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
|
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
|
||||||
|
|
||||||
var sceneObject = new NetworkObject.SceneObject();
|
var sceneObject = new NetworkObject.SceneObject();
|
||||||
@@ -703,6 +733,73 @@ namespace Unity.Netcode
|
|||||||
m_NetworkObjectsSync.Add(spawnedNetworkObject);
|
m_NetworkObjectsSync.Add(spawnedNetworkObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process all de-spawned in-scene NetworkObjects for this network session
|
||||||
|
m_DespawnedInSceneObjects.Clear();
|
||||||
|
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
|
||||||
|
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
|
||||||
|
|
||||||
|
for (int i = 0; i < despawnedObjectsCount; i++)
|
||||||
|
{
|
||||||
|
// We just need to get the scene
|
||||||
|
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
|
||||||
|
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
|
||||||
|
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
|
||||||
|
if (!sceneCache.ContainsKey(networkSceneHandle))
|
||||||
|
{
|
||||||
|
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
|
||||||
|
{
|
||||||
|
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
|
||||||
|
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
|
||||||
|
{
|
||||||
|
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
|
||||||
|
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) =>
|
||||||
|
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
|
||||||
|
|
||||||
|
foreach (var inSceneObject in inSceneNetworkObjects)
|
||||||
|
{
|
||||||
|
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
|
||||||
|
}
|
||||||
|
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
|
||||||
|
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Use the cached NetworkObjects if they exist
|
||||||
|
{
|
||||||
|
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
|
||||||
|
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
|
||||||
|
{
|
||||||
|
// Since this is a NetworkObject that was never spawned, we just need to send a notification
|
||||||
|
// out that it was despawned so users can make adjustments
|
||||||
|
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
|
||||||
|
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||||
|
{
|
||||||
|
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
|
||||||
|
{
|
||||||
|
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ namespace Unity.Netcode
|
|||||||
internal List<ulong> DoneClients { get; } = new List<ulong>();
|
internal List<ulong> DoneClients { get; } = new List<ulong>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The NetworkTime at the moment the scene switch was initiated by the server.
|
/// The local time when the scene event was "roughly started"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal NetworkTime TimeAtInitiation { get; }
|
internal float TimeAtInitiation { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
||||||
@@ -105,22 +105,40 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal LoadSceneMode LoadSceneMode;
|
internal LoadSceneMode LoadSceneMode;
|
||||||
|
|
||||||
|
internal List<ulong> ClientsThatStartedSceneEvent;
|
||||||
|
|
||||||
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
|
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
|
||||||
{
|
{
|
||||||
if (status == SceneEventProgressStatus.Started)
|
if (status == SceneEventProgressStatus.Started)
|
||||||
{
|
{
|
||||||
|
// Track the clients that were connected when we started this event
|
||||||
|
ClientsThatStartedSceneEvent = new List<ulong>(networkManager.ConnectedClientsIds);
|
||||||
m_NetworkManager = networkManager;
|
m_NetworkManager = networkManager;
|
||||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||||
TimeAtInitiation = networkManager.LocalTime;
|
TimeAtInitiation = Time.realtimeSinceStartup;
|
||||||
}
|
}
|
||||||
Status = status;
|
Status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coroutine that checks to see if the scene event is complete every network tick period.
|
||||||
|
/// This will handle completing the scene event when one or more client(s) disconnect(s)
|
||||||
|
/// during a scene event and if it does not complete within the scene loading time out period
|
||||||
|
/// it will time out the scene event.
|
||||||
|
/// </summary>
|
||||||
internal IEnumerator TimeOutSceneEventProgress()
|
internal IEnumerator TimeOutSceneEventProgress()
|
||||||
{
|
{
|
||||||
yield return new WaitForSecondsRealtime(m_NetworkManager.NetworkConfig.LoadSceneTimeOut);
|
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
|
||||||
TimedOut = true;
|
while (!TimedOut && !IsCompleted)
|
||||||
CheckCompletion();
|
{
|
||||||
|
yield return waitForNetworkTick;
|
||||||
|
|
||||||
|
CheckCompletion();
|
||||||
|
if (!IsCompleted)
|
||||||
|
{
|
||||||
|
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddClientAsDone(ulong clientId)
|
internal void AddClientAsDone(ulong clientId)
|
||||||
@@ -141,19 +159,49 @@ namespace Unity.Netcode
|
|||||||
m_SceneLoadOperation.completed += operation => CheckCompletion();
|
m_SceneLoadOperation.completed += operation => CheckCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called only on the server-side during integration test (NetcodeIntegrationTest specific)
|
||||||
|
/// scene loading and unloading.
|
||||||
|
///
|
||||||
|
/// Note: During integration testing we must queue all scene loading and unloading requests for
|
||||||
|
/// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid
|
||||||
|
/// conflicts when the <see cref="SceneManager.sceneLoaded"/> and <see cref="SceneManager.sceneUnloaded"/>
|
||||||
|
/// events are triggered. The Completed action simulates the <see cref="AsyncOperation.completed"/> event.
|
||||||
|
/// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler)
|
||||||
|
/// </summary>
|
||||||
|
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||||
|
{
|
||||||
|
sceneEventAction.Completed = SetComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes the SceneEventProgress
|
||||||
|
/// </summary>
|
||||||
|
internal void SetComplete()
|
||||||
|
{
|
||||||
|
IsCompleted = true;
|
||||||
|
AreAllClientsDoneLoading = true;
|
||||||
|
|
||||||
|
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
||||||
|
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
||||||
|
{
|
||||||
|
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||||
|
}
|
||||||
|
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
internal void CheckCompletion()
|
internal void CheckCompletion()
|
||||||
{
|
{
|
||||||
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && m_SceneLoadOperation.isDone) || (!IsCompleted && TimedOut))
|
try
|
||||||
{
|
{
|
||||||
IsCompleted = true;
|
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
|
||||||
AreAllClientsDoneLoading = true;
|
|
||||||
|
|
||||||
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
|
||||||
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
|
||||||
{
|
{
|
||||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
SetComplete();
|
||||||
}
|
}
|
||||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Two-way serializer wrapping FastBufferReader or FastBufferWriter.
|
/// Two-way serializer wrapping FastBufferReader or FastBufferWriter.
|
||||||
///
|
///
|
||||||
/// Implemented as a ref struct for two reasons:
|
/// Implemented as a ref struct for two reasons:
|
||||||
/// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash
|
/// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash
|
||||||
/// 2. The BufferSerializer must always be passed by reference and can't be copied
|
/// 2. The BufferSerializer must always be passed by reference and can't be copied
|
||||||
///
|
///
|
||||||
/// Ref structs help enforce both of those rules: they can't out live the stack context in which they were
|
/// Ref structs help enforce both of those rules: they can't ref live the stack context in which they were
|
||||||
/// created, and they're always passed by reference no matter what.
|
/// created, and they're always passed by reference no matter what.
|
||||||
///
|
///
|
||||||
/// BufferSerializer doesn't wrapp FastBufferReader or FastBufferWriter directly because it can't.
|
/// BufferSerializer doesn't wrapp FastBufferReader or FastBufferWriter directly because it can't.
|
||||||
@@ -58,168 +62,89 @@ namespace Unity.Netcode
|
|||||||
return m_Implementation.GetFastBufferWriter();
|
return m_Implementation.GetFastBufferWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValue(ref s, oneByteChars);
|
||||||
/// Serialize an INetworkSerializable
|
public void SerializeValue(ref byte value) => m_Implementation.SerializeValue(ref value);
|
||||||
///
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
|
||||||
/// Throws OverflowException if the end of the buffer has been reached.
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
|
||||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||||
/// </summary>
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||||
/// <param name="value">Value to serialize</param>
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||||
{
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||||
m_Implementation.SerializeNetworkSerializable(ref value);
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||||
}
|
public void SerializeValue(ref Vector2 value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector2[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector3 value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector3[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector2Int value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector2Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector3Int value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector3Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector4 value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Vector4[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Quaternion value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Quaternion[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Color value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Color[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Color32 value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Color32[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Ray value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Ray[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Ray2D value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
public void SerializeValue(ref Ray2D[] value) => m_Implementation.SerializeValue(ref value);
|
||||||
|
|
||||||
/// <summary>
|
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||||
/// Serialize a string.
|
// INativeList<bool> provides the Length property
|
||||||
///
|
// IUTF8Bytes provides GetUnsafePtr()
|
||||||
/// Note: Will ALWAYS allocate a new string when reading.
|
// Those two are necessary to serialize FixedStrings efficiently
|
||||||
///
|
// - otherwise we'd just be memcpying the whole thing even if
|
||||||
/// Throws OverflowException if the end of the buffer has been reached.
|
// most of it isn't used.
|
||||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
/// </summary>
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValue(ref value);
|
||||||
/// <param name="s">Value to serialize</param>
|
|
||||||
/// <param name="oneByteChars">
|
|
||||||
/// If true, will truncate each char to one byte.
|
|
||||||
/// This is slower than two-byte chars, but uses less bandwidth.
|
|
||||||
/// </param>
|
|
||||||
public void SerializeValue(ref string s, bool oneByteChars = false)
|
|
||||||
{
|
|
||||||
m_Implementation.SerializeValue(ref s, oneByteChars);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value);
|
||||||
/// Serialize an array value.
|
|
||||||
///
|
|
||||||
/// Note: Will ALWAYS allocate a new array when reading.
|
|
||||||
/// If you have a statically-sized array that you know is large enough, it's recommended to
|
|
||||||
/// serialize the size yourself and iterate serializing array members.
|
|
||||||
///
|
|
||||||
/// (This is because C# doesn't allow setting an array's length value, so deserializing
|
|
||||||
/// into an existing array of larger size would result in an array that doesn't have as many values
|
|
||||||
/// as its Length indicates it should.)
|
|
||||||
///
|
|
||||||
/// Throws OverflowException if the end of the buffer has been reached.
|
|
||||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="array">Value to serialize</param>
|
|
||||||
public void SerializeValue<T>(ref T[] array) where T : unmanaged
|
|
||||||
{
|
|
||||||
m_Implementation.SerializeValue(ref array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize a single byte
|
|
||||||
///
|
|
||||||
/// Throws OverflowException if the end of the buffer has been reached.
|
|
||||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">Value to serialize</param>
|
|
||||||
public void SerializeValue(ref byte value)
|
|
||||||
{
|
|
||||||
m_Implementation.SerializeValue(ref value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize an unmanaged type. Supports basic value types as well as structs.
|
|
||||||
/// The provided type will be copied to/from the buffer as it exists in memory.
|
|
||||||
///
|
|
||||||
/// Throws OverflowException if the end of the buffer has been reached.
|
|
||||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">Value to serialize</param>
|
|
||||||
public void SerializeValue<T>(ref T value) where T : unmanaged
|
|
||||||
{
|
|
||||||
m_Implementation.SerializeValue(ref value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Allows faster serialization by batching bounds checking.
|
|
||||||
/// When you know you will be writing multiple fields back-to-back and you know the total size,
|
|
||||||
/// you can call PreCheck() once on the total size, and then follow it with calls to
|
|
||||||
/// SerializeValuePreChecked() for faster serialization. Write buffers will grow during PreCheck()
|
|
||||||
/// if needed.
|
|
||||||
///
|
|
||||||
/// PreChecked serialization operations will throw OverflowException in editor and development builds if you
|
|
||||||
/// go past the point you've marked using PreCheck(). In release builds, OverflowException will not be thrown
|
|
||||||
/// for performance reasons, since the point of using PreCheck is to avoid bounds checking in the following
|
|
||||||
/// operations in release builds.
|
|
||||||
///
|
|
||||||
/// To get the correct size to check for, use FastBufferWriter.GetWriteSize(value) or
|
|
||||||
/// FastBufferWriter.GetWriteSize<type>()
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="amount">Number of bytes you plan to read or write</param>
|
|
||||||
/// <returns>True if the read/write can proceed, false otherwise.</returns>
|
|
||||||
public bool PreCheck(int amount)
|
public bool PreCheck(int amount)
|
||||||
{
|
{
|
||||||
return m_Implementation.PreCheck(amount);
|
return m_Implementation.PreCheck(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
|
||||||
/// Serialize a string.
|
public void SerializeValuePreChecked(ref byte value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
///
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// Note: Will ALWAYS allocate a new string when reading.
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
///
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// </summary>
|
public void SerializeValuePreChecked(ref Vector2 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// <param name="s">Value to serialize</param>
|
public void SerializeValuePreChecked(ref Vector2[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// <param name="oneByteChars">
|
public void SerializeValuePreChecked(ref Vector3 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// If true, will truncate each char to one byte.
|
public void SerializeValuePreChecked(ref Vector3[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// This is slower than two-byte chars, but uses less bandwidth.
|
public void SerializeValuePreChecked(ref Vector2Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// </param>
|
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
|
public void SerializeValuePreChecked(ref Vector3Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
{
|
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
|
public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
}
|
public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Color value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Color[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Color32 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Color32[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
|
|
||||||
/// <summary>
|
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||||
/// Serialize an array value.
|
// INativeList<bool> provides the Length property
|
||||||
///
|
// IUTF8Bytes provides GetUnsafePtr()
|
||||||
/// Note: Will ALWAYS allocate a new array when reading.
|
// Those two are necessary to serialize FixedStrings efficiently
|
||||||
/// If you have a statically-sized array that you know is large enough, it's recommended to
|
// - otherwise we'd just be memcpying the whole thing even if
|
||||||
/// serialize the size yourself and iterate serializing array members.
|
// most of it isn't used.
|
||||||
///
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
/// (This is because C# doesn't allow setting an array's length value, so deserializing
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValuePreChecked(ref value);
|
||||||
/// into an existing array of larger size would result in an array that doesn't have as many values
|
|
||||||
/// as its Length indicates it should.)
|
|
||||||
///
|
|
||||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
|
||||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
|
||||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="array">Value to serialize</param>
|
|
||||||
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
|
|
||||||
{
|
|
||||||
m_Implementation.SerializeValuePreChecked(ref array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize a single byte
|
|
||||||
///
|
|
||||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
|
||||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
|
||||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">Value to serialize</param>
|
|
||||||
public void SerializeValuePreChecked(ref byte value)
|
|
||||||
{
|
|
||||||
m_Implementation.SerializeValuePreChecked(ref value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize an unmanaged type. Supports basic value types as well as structs.
|
|
||||||
/// The provided type will be copied to/from the buffer as it exists in memory.
|
|
||||||
///
|
|
||||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
|
||||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
|
||||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">Value to serialize</param>
|
|
||||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
|
||||||
{
|
|
||||||
m_Implementation.SerializeValuePreChecked(ref value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -24,54 +26,77 @@ namespace Unity.Netcode
|
|||||||
throw new InvalidOperationException("Cannot retrieve a FastBufferWriter from a serializer where IsWriter = false");
|
throw new InvalidOperationException("Cannot retrieve a FastBufferWriter from a serializer where IsWriter = false");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SerializeValue(ref string s, bool oneByteChars = false)
|
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Reader.ReadValueSafe(out s, oneByteChars);
|
||||||
{
|
public void SerializeValue(ref byte value) => m_Reader.ReadByteSafe(out value);
|
||||||
m_Reader.ReadValueSafe(out s, oneByteChars);
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValueSafe(out value);
|
||||||
}
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||||
|
|
||||||
public void SerializeValue<T>(ref T[] array) where T : unmanaged
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
{
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValueSafe(out value);
|
||||||
m_Reader.ReadValueSafe(out array);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SerializeValue(ref byte value)
|
public void SerializeValue(ref Vector2 value) => m_Reader.ReadValueSafe(out value);
|
||||||
{
|
public void SerializeValue(ref Vector2[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
m_Reader.ReadByteSafe(out value);
|
public void SerializeValue(ref Vector3 value) => m_Reader.ReadValueSafe(out value);
|
||||||
}
|
public void SerializeValue(ref Vector3[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Vector2Int value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Vector2Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Vector3Int value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Vector3Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Vector4 value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Vector4[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Quaternion value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Quaternion[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Color value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Color[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Color32 value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Color32[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Ray value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Ray[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Ray2D value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
public void SerializeValue(ref Ray2D[] value) => m_Reader.ReadValueSafe(out value);
|
||||||
|
|
||||||
public void SerializeValue<T>(ref T value) where T : unmanaged
|
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializable(out value);
|
||||||
{
|
|
||||||
m_Reader.ReadValueSafe(out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
|
|
||||||
{
|
|
||||||
m_Reader.ReadNetworkSerializable(out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool PreCheck(int amount)
|
public bool PreCheck(int amount)
|
||||||
{
|
{
|
||||||
return m_Reader.TryBeginRead(amount);
|
return m_Reader.TryBeginRead(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
|
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Reader.ReadValue(out s, oneByteChars);
|
||||||
{
|
public void SerializeValuePreChecked(ref byte value) => m_Reader.ReadByte(out value);
|
||||||
m_Reader.ReadValue(out s, oneByteChars);
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValue(out value);
|
||||||
}
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value);
|
||||||
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value);
|
||||||
{
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||||
m_Reader.ReadValue(out array);
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||||
}
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValue(out value);
|
||||||
public void SerializeValuePreChecked(ref byte value)
|
public void SerializeValuePreChecked(ref Vector2 value) => m_Reader.ReadValue(out value);
|
||||||
{
|
public void SerializeValuePreChecked(ref Vector2[] value) => m_Reader.ReadValue(out value);
|
||||||
m_Reader.ReadValue(out value);
|
public void SerializeValuePreChecked(ref Vector3 value) => m_Reader.ReadValue(out value);
|
||||||
}
|
public void SerializeValuePreChecked(ref Vector3[] value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Vector2Int value) => m_Reader.ReadValue(out value);
|
||||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Reader.ReadValue(out value);
|
||||||
{
|
public void SerializeValuePreChecked(ref Vector3Int value) => m_Reader.ReadValue(out value);
|
||||||
m_Reader.ReadValue(out value);
|
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Reader.ReadValue(out value);
|
||||||
}
|
public void SerializeValuePreChecked(ref Vector4 value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Vector4[] value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Quaternion value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Color value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Color[] value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Color32 value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Color32[] value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray[] value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray2D value) => m_Reader.ReadValue(out value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Reader.ReadValue(out value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -24,25 +26,39 @@ namespace Unity.Netcode
|
|||||||
return m_Writer;
|
return m_Writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SerializeValue(ref string s, bool oneByteChars = false)
|
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Writer.WriteValueSafe(s, oneByteChars);
|
||||||
{
|
public void SerializeValue(ref byte value) => m_Writer.WriteByteSafe(value);
|
||||||
m_Writer.WriteValueSafe(s, oneByteChars);
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValueSafe(value);
|
||||||
}
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValueSafe(value);
|
||||||
|
|
||||||
public void SerializeValue<T>(ref T[] array) where T : unmanaged
|
public void SerializeValue(ref Vector2 value) => m_Writer.WriteValueSafe(value);
|
||||||
{
|
public void SerializeValue(ref Vector2[] value) => m_Writer.WriteValueSafe(value);
|
||||||
m_Writer.WriteValueSafe(array);
|
public void SerializeValue(ref Vector3 value) => m_Writer.WriteValueSafe(value);
|
||||||
}
|
public void SerializeValue(ref Vector3[] value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Vector2Int value) => m_Writer.WriteValueSafe(value);
|
||||||
public void SerializeValue(ref byte value)
|
public void SerializeValue(ref Vector2Int[] value) => m_Writer.WriteValueSafe(value);
|
||||||
{
|
public void SerializeValue(ref Vector3Int value) => m_Writer.WriteValueSafe(value);
|
||||||
m_Writer.WriteByteSafe(value);
|
public void SerializeValue(ref Vector3Int[] value) => m_Writer.WriteValueSafe(value);
|
||||||
}
|
public void SerializeValue(ref Vector4 value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Vector4[] value) => m_Writer.WriteValueSafe(value);
|
||||||
public void SerializeValue<T>(ref T value) where T : unmanaged
|
public void SerializeValue(ref Quaternion value) => m_Writer.WriteValueSafe(value);
|
||||||
{
|
public void SerializeValue(ref Quaternion[] value) => m_Writer.WriteValueSafe(value);
|
||||||
m_Writer.WriteValueSafe(value);
|
public void SerializeValue(ref Color value) => m_Writer.WriteValueSafe(value);
|
||||||
}
|
public void SerializeValue(ref Color[] value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Color32 value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Color32[] value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Ray value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Ray[] value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Ray2D value) => m_Writer.WriteValueSafe(value);
|
||||||
|
public void SerializeValue(ref Ray2D[] value) => m_Writer.WriteValueSafe(value);
|
||||||
|
|
||||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
|
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
|
||||||
{
|
{
|
||||||
@@ -54,24 +70,37 @@ namespace Unity.Netcode
|
|||||||
return m_Writer.TryBeginWrite(amount);
|
return m_Writer.TryBeginWrite(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
|
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Writer.WriteValue(s, oneByteChars);
|
||||||
{
|
public void SerializeValuePreChecked(ref byte value) => m_Writer.WriteByte(value);
|
||||||
m_Writer.WriteValue(s, oneByteChars);
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValue(value);
|
||||||
}
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValue(value);
|
||||||
|
|
||||||
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value);
|
||||||
{
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value);
|
||||||
m_Writer.WriteValue(array);
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||||
}
|
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValue(value);
|
||||||
|
|
||||||
public void SerializeValuePreChecked(ref byte value)
|
public void SerializeValuePreChecked(ref Vector2 value) => m_Writer.WriteValue(value);
|
||||||
{
|
public void SerializeValuePreChecked(ref Vector2[] value) => m_Writer.WriteValue(value);
|
||||||
m_Writer.WriteByte(value);
|
public void SerializeValuePreChecked(ref Vector3 value) => m_Writer.WriteValue(value);
|
||||||
}
|
public void SerializeValuePreChecked(ref Vector3[] value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Vector2Int value) => m_Writer.WriteValue(value);
|
||||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Writer.WriteValue(value);
|
||||||
{
|
public void SerializeValuePreChecked(ref Vector3Int value) => m_Writer.WriteValue(value);
|
||||||
m_Writer.WriteValue(value);
|
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Writer.WriteValue(value);
|
||||||
}
|
public void SerializeValuePreChecked(ref Vector4 value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Vector4[] value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Quaternion value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Color value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Color[] value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Color32 value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Color32[] value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray[] value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray2D value) => m_Writer.WriteValue(value);
|
||||||
|
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Writer.WriteValue(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Collections.LowLevel.Unsafe;
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -53,25 +54,29 @@ namespace Unity.Netcode
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator allocator)
|
private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator copyAllocator, Allocator internalAllocator)
|
||||||
{
|
{
|
||||||
ReaderHandle* readerHandle = null;
|
ReaderHandle* readerHandle = null;
|
||||||
if (allocator == Allocator.None)
|
if (copyAllocator == Allocator.None)
|
||||||
{
|
{
|
||||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), Allocator.Temp);
|
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), internalAllocator);
|
||||||
readerHandle->BufferPointer = buffer;
|
readerHandle->BufferPointer = buffer;
|
||||||
readerHandle->Position = offset;
|
readerHandle->Position = offset;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), allocator);
|
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), copyAllocator);
|
||||||
UnsafeUtility.MemCpy(readerHandle + 1, buffer + offset, length);
|
UnsafeUtility.MemCpy(readerHandle + 1, buffer + offset, length);
|
||||||
readerHandle->BufferPointer = (byte*)(readerHandle + 1);
|
readerHandle->BufferPointer = (byte*)(readerHandle + 1);
|
||||||
readerHandle->Position = 0;
|
readerHandle->Position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
readerHandle->Length = length;
|
readerHandle->Length = length;
|
||||||
readerHandle->Allocator = allocator;
|
|
||||||
|
// If the copyAllocator provided is Allocator.None, there is a chance that the internalAllocator was provided
|
||||||
|
// When we dispose, we are really only interested in disposing Allocator.Persistent and Allocator.TempJob
|
||||||
|
// as disposing Allocator.Temp and Allocator.None would do nothing. Therefore, make sure we dispose the readerHandle with the right Allocator label
|
||||||
|
readerHandle->Allocator = copyAllocator == Allocator.None ? internalAllocator : copyAllocator;
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
readerHandle->AllowedReadMark = 0;
|
readerHandle->AllowedReadMark = 0;
|
||||||
readerHandle->InBitwiseContext = false;
|
readerHandle->InBitwiseContext = false;
|
||||||
@@ -82,23 +87,26 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a FastBufferReader from a NativeArray.
|
/// Create a FastBufferReader from a NativeArray.
|
||||||
///
|
///
|
||||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||||
/// FastBufferReader will then own the data.
|
/// FastBufferReader will then own the data.
|
||||||
///
|
///
|
||||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
/// Allocator.Temp so it should be treated as if it's a ref struct and not allowed to outlive
|
||||||
/// the context in which it was created (it should neither be returned from that function nor
|
/// the context in which it was created (it should neither be returned from that function nor
|
||||||
/// stored anywhere in heap memory).
|
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set
|
||||||
|
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||||
|
/// should manually call Dispose() when it is no longer needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buffer"></param>
|
/// <param name="buffer"></param>
|
||||||
/// <param name="allocator"></param>
|
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||||
/// <param name="length"></param>
|
/// <param name="length"></param>
|
||||||
/// <param name="offset"></param>
|
/// <param name="offset"></param>
|
||||||
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
|
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||||
|
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||||
{
|
{
|
||||||
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, allocator);
|
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, copyAllocator, internalAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -111,18 +119,18 @@ namespace Unity.Netcode
|
|||||||
/// and ensure the FastBufferReader isn't used outside that block.
|
/// and ensure the FastBufferReader isn't used outside that block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buffer">The buffer to copy from</param>
|
/// <param name="buffer">The buffer to copy from</param>
|
||||||
/// <param name="allocator">The allocator to use</param>
|
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||||
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
|
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0)
|
||||||
{
|
{
|
||||||
if (allocator == Allocator.None)
|
if (copyAllocator == Allocator.None)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
||||||
}
|
}
|
||||||
fixed (byte* data = buffer.Array)
|
fixed (byte* data = buffer.Array)
|
||||||
{
|
{
|
||||||
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, allocator);
|
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, copyAllocator, Allocator.Temp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,63 +144,94 @@ namespace Unity.Netcode
|
|||||||
/// and ensure the FastBufferReader isn't used outside that block.
|
/// and ensure the FastBufferReader isn't used outside that block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buffer">The buffer to copy from</param>
|
/// <param name="buffer">The buffer to copy from</param>
|
||||||
/// <param name="allocator">The allocator to use</param>
|
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||||
public unsafe FastBufferReader(byte[] buffer, Allocator allocator, int length = -1, int offset = 0)
|
public unsafe FastBufferReader(byte[] buffer, Allocator copyAllocator, int length = -1, int offset = 0)
|
||||||
{
|
{
|
||||||
if (allocator == Allocator.None)
|
if (copyAllocator == Allocator.None)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
||||||
}
|
}
|
||||||
fixed (byte* data = buffer)
|
fixed (byte* data = buffer)
|
||||||
{
|
{
|
||||||
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, allocator);
|
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, copyAllocator, Allocator.Temp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a FastBufferReader from an existing byte buffer.
|
/// 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.
|
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||||
/// FastBufferReader will then own the data.
|
/// FastBufferReader will then own the data.
|
||||||
///
|
///
|
||||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||||
/// the context in which it was created (it should neither be returned from that function nor
|
/// the context in which it was created (it should neither be returned from that function nor
|
||||||
/// stored anywhere in heap memory).
|
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set
|
||||||
|
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||||
|
/// should manually call Dispose() when it is no longer needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buffer">The buffer to copy from</param>
|
/// <param name="buffer">The buffer to copy from</param>
|
||||||
/// <param name="allocator">The allocator to use</param>
|
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||||
/// <param name="length">The number of bytes to copy</param>
|
/// <param name="length">The number of bytes to copy</param>
|
||||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
/// <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)
|
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||||
|
public unsafe FastBufferReader(byte* buffer, Allocator copyAllocator, int length, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||||
{
|
{
|
||||||
Handle = CreateHandle(buffer, length, offset, allocator);
|
Handle = CreateHandle(buffer, length, offset, copyAllocator, internalAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a FastBufferReader from a FastBufferWriter.
|
/// Create a FastBufferReader from a FastBufferWriter.
|
||||||
///
|
///
|
||||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||||
/// FastBufferReader will then own the data.
|
/// FastBufferReader will then own the data.
|
||||||
///
|
///
|
||||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||||
|
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||||
|
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||||
|
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||||
|
/// the context in which it was created (it should neither be returned from that function nor
|
||||||
|
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set
|
||||||
|
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||||
|
/// should manually call Dispose() when it is no longer needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The writer to copy from</param>
|
||||||
|
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||||
|
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||||
|
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||||
|
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||||
|
public unsafe FastBufferReader(FastBufferWriter writer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||||
|
{
|
||||||
|
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, copyAllocator, internalAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a FastBufferReader from another existing FastBufferReader. This is typically used when you
|
||||||
|
/// want to change the copyAllocator that a reader is allocated to - for example, upgrading a Temp reader to
|
||||||
|
/// a Persistent one to be processed later.
|
||||||
|
///
|
||||||
|
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||||
|
/// FastBufferReader will then own the data.
|
||||||
|
///
|
||||||
|
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||||
/// the context in which it was created (it should neither be returned from that function nor
|
/// the context in which it was created (it should neither be returned from that function nor
|
||||||
/// stored anywhere in heap memory).
|
/// stored anywhere in heap memory).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="writer">The writer to copy from</param>
|
/// <param name="reader">The reader to copy from</param>
|
||||||
/// <param name="allocator">The allocator to use</param>
|
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
/// <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)
|
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||||
|
public unsafe FastBufferReader(FastBufferReader reader, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||||
{
|
{
|
||||||
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator);
|
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, copyAllocator, internalAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -492,61 +531,6 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes an unmanaged array
|
|
||||||
/// NOTE: ALLOCATES
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="array">Stores the read array</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public unsafe void ReadValue<T>(out T[] array) where T : unmanaged
|
|
||||||
{
|
|
||||||
ReadValue(out int sizeInTs);
|
|
||||||
int sizeInBytes = sizeInTs * sizeof(T);
|
|
||||||
array = new T[sizeInTs];
|
|
||||||
fixed (T* native = array)
|
|
||||||
{
|
|
||||||
byte* bytes = (byte*)(native);
|
|
||||||
ReadBytes(bytes, sizeInBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads an unmanaged array
|
|
||||||
/// NOTE: ALLOCATES
|
|
||||||
///
|
|
||||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
|
||||||
/// for multiple reads at once by calling TryBeginRead.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="array">Stores the read array</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public unsafe void ReadValueSafe<T>(out T[] array) where T : unmanaged
|
|
||||||
{
|
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
||||||
if (Handle->InBitwiseContext)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!TryBeginReadInternal(sizeof(int)))
|
|
||||||
{
|
|
||||||
throw new OverflowException("Reading past the end of the buffer");
|
|
||||||
}
|
|
||||||
ReadValue(out int sizeInTs);
|
|
||||||
int sizeInBytes = sizeInTs * sizeof(T);
|
|
||||||
if (!TryBeginReadInternal(sizeInBytes))
|
|
||||||
{
|
|
||||||
throw new OverflowException("Reading past the end of the buffer");
|
|
||||||
}
|
|
||||||
array = new T[sizeInTs];
|
|
||||||
fixed (T* native = array)
|
|
||||||
{
|
|
||||||
byte* bytes = (byte*)(native);
|
|
||||||
ReadBytes(bytes, sizeInBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it.
|
/// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -711,69 +695,197 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read a value of any unmanaged type to the buffer.
|
|
||||||
/// It will be copied from the buffer exactly as it existed in memory on the writing end.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The read value</param>
|
|
||||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public unsafe void ReadValue<T>(out T value) where T : unmanaged
|
internal unsafe void ReadUnmanaged<T>(out T value) where T : unmanaged
|
||||||
{
|
{
|
||||||
int len = sizeof(T);
|
|
||||||
|
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
||||||
if (Handle->InBitwiseContext)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
|
|
||||||
}
|
|
||||||
if (Handle->Position + len > Handle->AllowedReadMark)
|
|
||||||
{
|
|
||||||
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
fixed (T* ptr = &value)
|
fixed (T* ptr = &value)
|
||||||
{
|
{
|
||||||
UnsafeUtility.MemCpy((byte*)ptr, Handle->BufferPointer + Handle->Position, len);
|
byte* bytes = (byte*)ptr;
|
||||||
|
ReadBytes(bytes, sizeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal unsafe void ReadUnmanagedSafe<T>(out T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
fixed (T* ptr = &value)
|
||||||
|
{
|
||||||
|
byte* bytes = (byte*)ptr;
|
||||||
|
ReadBytesSafe(bytes, sizeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal unsafe void ReadUnmanaged<T>(out T[] value) where T : unmanaged
|
||||||
|
{
|
||||||
|
ReadUnmanaged(out int sizeInTs);
|
||||||
|
int sizeInBytes = sizeInTs * sizeof(T);
|
||||||
|
value = new T[sizeInTs];
|
||||||
|
fixed (T* ptr = value)
|
||||||
|
{
|
||||||
|
byte* bytes = (byte*)ptr;
|
||||||
|
ReadBytes(bytes, sizeInBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal unsafe void ReadUnmanagedSafe<T>(out T[] value) where T : unmanaged
|
||||||
|
{
|
||||||
|
ReadUnmanagedSafe(out int sizeInTs);
|
||||||
|
int sizeInBytes = sizeInTs * sizeof(T);
|
||||||
|
value = new T[sizeInTs];
|
||||||
|
fixed (T* ptr = value)
|
||||||
|
{
|
||||||
|
byte* bytes = (byte*)ptr;
|
||||||
|
ReadBytesSafe(bytes, sizeInBytes);
|
||||||
}
|
}
|
||||||
Handle->Position += len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read a value of any unmanaged type to the buffer.
|
|
||||||
/// It will be copied from the buffer exactly as it existed in memory on the writing end.
|
|
||||||
///
|
|
||||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
|
||||||
/// for multiple reads at once by calling TryBeginRead.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The read value</param>
|
|
||||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public unsafe void ReadValueSafe<T>(out T value) where T : unmanaged
|
public void ReadValue<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
||||||
|
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
||||||
|
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector2 value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector2[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector3 value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector3[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector2Int value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector2Int[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector3Int value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector3Int[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector4 value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Vector4[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Quaternion value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Quaternion[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Color value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Color[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Color32 value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Color32[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Ray value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Ray[] value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Ray2D value) => ReadUnmanaged(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValue(out Ray2D[] value) => ReadUnmanaged(out value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector2 value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector2[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector3 value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector3[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector2Int value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector2Int[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector3Int value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector3Int[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector4 value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Vector4[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Quaternion value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Quaternion[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Color value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Color[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Color32 value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Color32[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Ray value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Ray[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Ray2D value) => ReadUnmanagedSafe(out value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void ReadValueSafe(out Ray2D[] value) => ReadUnmanagedSafe(out value);
|
||||||
|
|
||||||
|
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||||
|
// INativeList<bool> provides the Length property
|
||||||
|
// IUTF8Bytes provides GetUnsafePtr()
|
||||||
|
// Those two are necessary to serialize FixedStrings efficiently
|
||||||
|
// - otherwise we'd just be memcpying the whole thing even if
|
||||||
|
// most of it isn't used.
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe void ReadValue<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
{
|
{
|
||||||
int len = sizeof(T);
|
ReadUnmanaged(out int length);
|
||||||
|
value = new T();
|
||||||
|
value.Length = length;
|
||||||
|
ReadBytes(value.GetUnsafePtr(), length);
|
||||||
|
}
|
||||||
|
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
if (Handle->InBitwiseContext)
|
public unsafe void ReadValueSafe<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
{
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
throw new InvalidOperationException(
|
{
|
||||||
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
|
ReadUnmanagedSafe(out int length);
|
||||||
}
|
value = new T();
|
||||||
#endif
|
value.Length = length;
|
||||||
|
ReadBytesSafe(value.GetUnsafePtr(), length);
|
||||||
if (!TryBeginReadInternal(len))
|
|
||||||
{
|
|
||||||
throw new OverflowException("Reading past the end of the buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fixed (T* ptr = &value)
|
|
||||||
{
|
|
||||||
UnsafeUtility.MemCpy((byte*)ptr, Handle->BufferPointer + Handle->Position, len);
|
|
||||||
}
|
|
||||||
Handle->Position += len;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Collections.LowLevel.Unsafe;
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -528,60 +529,6 @@ namespace Unity.Netcode
|
|||||||
return sizeof(int) + sizeInBytes;
|
return sizeof(int) + sizeInBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes an unmanaged array
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="array">The array to write</param>
|
|
||||||
/// <param name="count">The amount of elements to write</param>
|
|
||||||
/// <param name="offset">Where in the array to start</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public unsafe void WriteValue<T>(T[] array, int count = -1, int offset = 0) where T : unmanaged
|
|
||||||
{
|
|
||||||
int sizeInTs = count != -1 ? count : array.Length - offset;
|
|
||||||
int sizeInBytes = sizeInTs * sizeof(T);
|
|
||||||
WriteValue(sizeInTs);
|
|
||||||
fixed (T* native = array)
|
|
||||||
{
|
|
||||||
byte* bytes = (byte*)(native + offset);
|
|
||||||
WriteBytes(bytes, sizeInBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes an unmanaged array
|
|
||||||
///
|
|
||||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
|
||||||
/// for multiple writes at once by calling TryBeginWrite.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="array">The array to write</param>
|
|
||||||
/// <param name="count">The amount of elements to write</param>
|
|
||||||
/// <param name="offset">Where in the array to start</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public unsafe void WriteValueSafe<T>(T[] array, int count = -1, int offset = 0) where T : unmanaged
|
|
||||||
{
|
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
||||||
if (Handle->InBitwiseContext)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int sizeInTs = count != -1 ? count : array.Length - offset;
|
|
||||||
int sizeInBytes = sizeInTs * sizeof(T);
|
|
||||||
|
|
||||||
if (!TryBeginWriteInternal(sizeInBytes + sizeof(int)))
|
|
||||||
{
|
|
||||||
throw new OverflowException("Writing past the end of the buffer");
|
|
||||||
}
|
|
||||||
WriteValue(sizeInTs);
|
|
||||||
fixed (T* native = array)
|
|
||||||
{
|
|
||||||
byte* bytes = (byte*)(native + offset);
|
|
||||||
WriteBytes(bytes, sizeInBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a partial value. The specified number of bytes is written from the value and the rest is ignored.
|
/// Write a partial value. The specified number of bytes is written from the value and the rest is ignored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -769,15 +716,30 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the size required to write an unmanaged value
|
/// Get the write size for any general unmanaged value
|
||||||
|
/// The ForStructs value here makes this the lowest-priority overload so other versions
|
||||||
|
/// will be prioritized over this if they match
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="unused"></param>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static unsafe int GetWriteSize<T>(in T value, ForStructs unused = default) where T : unmanaged
|
||||||
|
{
|
||||||
|
return sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the write size for a FixedString
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public static int GetWriteSize<T>(in T value)
|
||||||
public static unsafe int GetWriteSize<T>(in T value) where T : unmanaged
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
{
|
{
|
||||||
return sizeof(T);
|
return value.Length + sizeof(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -790,68 +752,223 @@ namespace Unity.Netcode
|
|||||||
return sizeof(T);
|
return sizeof(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
|
|
||||||
/// It will be copied into the buffer exactly as it exists in memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The value to copy</param>
|
|
||||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public unsafe void WriteValue<T>(in T value) where T : unmanaged
|
internal unsafe void WriteUnmanaged<T>(in T value) where T : unmanaged
|
||||||
{
|
{
|
||||||
int len = sizeof(T);
|
|
||||||
|
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
||||||
if (Handle->InBitwiseContext)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
|
|
||||||
}
|
|
||||||
if (Handle->Position + len > Handle->AllowedWriteMark)
|
|
||||||
{
|
|
||||||
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
fixed (T* ptr = &value)
|
fixed (T* ptr = &value)
|
||||||
{
|
{
|
||||||
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len);
|
byte* bytes = (byte*)ptr;
|
||||||
|
WriteBytes(bytes, sizeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal unsafe void WriteUnmanagedSafe<T>(in T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
fixed (T* ptr = &value)
|
||||||
|
{
|
||||||
|
byte* bytes = (byte*)ptr;
|
||||||
|
WriteBytesSafe(bytes, sizeof(T));
|
||||||
}
|
}
|
||||||
Handle->Position += len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
|
|
||||||
/// It will be copied into the buffer exactly as it exists in memory.
|
|
||||||
///
|
|
||||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
|
||||||
/// for multiple writes at once by calling TryBeginWrite.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The value to copy</param>
|
|
||||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public unsafe void WriteValueSafe<T>(in T value) where T : unmanaged
|
internal unsafe void WriteUnmanaged<T>(T[] value) where T : unmanaged
|
||||||
{
|
{
|
||||||
int len = sizeof(T);
|
WriteUnmanaged(value.Length);
|
||||||
|
fixed (T* ptr = value)
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
||||||
if (Handle->InBitwiseContext)
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
byte* bytes = (byte*)ptr;
|
||||||
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
|
WriteBytes(bytes, sizeof(T) * value.Length);
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal unsafe void WriteUnmanagedSafe<T>(T[] value) where T : unmanaged
|
||||||
|
{
|
||||||
|
WriteUnmanagedSafe(value.Length);
|
||||||
|
fixed (T* ptr = value)
|
||||||
|
{
|
||||||
|
byte* bytes = (byte*)ptr;
|
||||||
|
WriteBytesSafe(bytes, sizeof(T) * value.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!TryBeginWriteInternal(len))
|
// These structs enable overloading of WriteValue with different generic constraints.
|
||||||
|
// The compiler's actually able to distinguish between overloads based on generic constraints.
|
||||||
|
// But at the bytecode level, the constraints aren't included in the method signature.
|
||||||
|
// By adding a second parameter with a defaulted value, the signatures of each generic are different,
|
||||||
|
// thus allowing overloads of methods based on the first parameter meeting constraints.
|
||||||
|
public struct ForPrimitives
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ForEnums
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ForStructs
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ForNetworkSerializable
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ForFixedStrings
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Vector2 value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Vector2[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Vector3 value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Vector3[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Vector2Int value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Vector2Int[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Vector3Int value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Vector3Int[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Vector4 value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Vector4[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Quaternion value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Quaternion[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Color value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Color[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Color32 value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Color32[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Ray value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Ray[] value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(in Ray2D value) => WriteUnmanaged(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValue(Ray2D[] value) => WriteUnmanaged(value);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Vector2[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Vector3 value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Vector2Int value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Vector2Int[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Vector3Int value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Vector3Int[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Quaternion value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Quaternion[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Color value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Color[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Color32 value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Color32[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Ray value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Ray[] value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(value);
|
||||||
|
|
||||||
|
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||||
|
// INativeList<bool> provides the Length property
|
||||||
|
// IUTF8Bytes provides GetUnsafePtr()
|
||||||
|
// Those two are necessary to serialize FixedStrings efficiently
|
||||||
|
// - otherwise we'd just be memcpying the whole thing even if
|
||||||
|
// most of it isn't used.
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe void WriteValue<T>(in T value, ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
|
{
|
||||||
|
WriteUnmanaged(value.Length);
|
||||||
|
// This avoids a copy on the string, which could be costly for FixedString4096Bytes
|
||||||
|
// Otherwise, GetUnsafePtr() is an impure function call and will result in a copy
|
||||||
|
// for `in` parameters.
|
||||||
|
fixed (T* ptr = &value)
|
||||||
|
{
|
||||||
|
WriteBytes(ptr->GetUnsafePtr(), value.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteValueSafe<T>(in T value, ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
|
{
|
||||||
|
if (!TryBeginWriteInternal(sizeof(int) + value.Length))
|
||||||
{
|
{
|
||||||
throw new OverflowException("Writing past the end of the buffer");
|
throw new OverflowException("Writing past the end of the buffer");
|
||||||
}
|
}
|
||||||
|
WriteValue(value);
|
||||||
fixed (T* ptr = &value)
|
|
||||||
{
|
|
||||||
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len);
|
|
||||||
}
|
|
||||||
Handle->Position += len;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs
Normal file
37
Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is a wrapper that adds `INetworkSerializeByMemcpy` support to existing structs that the developer
|
||||||
|
/// doesn't have the ability to modify (for example, external structs like `Guid`).
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public struct ForceNetworkSerializeByMemcpy<T> : INetworkSerializeByMemcpy, IEquatable<ForceNetworkSerializeByMemcpy<T>> where T : unmanaged, IEquatable<T>
|
||||||
|
{
|
||||||
|
public T Value;
|
||||||
|
|
||||||
|
public ForceNetworkSerializeByMemcpy(T value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator T(ForceNetworkSerializeByMemcpy<T> container) => container.Value;
|
||||||
|
public static implicit operator ForceNetworkSerializeByMemcpy<T>(T underlyingValue) => new ForceNetworkSerializeByMemcpy<T> { Value = underlyingValue };
|
||||||
|
|
||||||
|
public bool Equals(ForceNetworkSerializeByMemcpy<T> other)
|
||||||
|
{
|
||||||
|
return Value.Equals(other.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is ForceNetworkSerializeByMemcpy<T> other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Value.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d56016695cd44430a345671f7d56b18e
|
||||||
|
timeCreated: 1647635768
|
||||||
15
Runtime/Serialization/INetworkSerializeByMemcpy.cs
Normal file
15
Runtime/Serialization/INetworkSerializeByMemcpy.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This interface is a "tag" that can be applied to a struct to mark that struct as being serializable
|
||||||
|
/// by memcpy. It's up to the developer of the struct to analyze the struct's contents and ensure it
|
||||||
|
/// is actually serializable by memcpy. This requires all of the members of the struct to be
|
||||||
|
/// `unmanaged` Plain-Old-Data values - if your struct contains a pointer (or a type that contains a pointer,
|
||||||
|
/// like `NativeList<T>`), it should be serialized via `INetworkSerializable` or via
|
||||||
|
/// `FastBufferReader`/`FastBufferWriter` extension methods.
|
||||||
|
/// </summary>
|
||||||
|
public interface INetworkSerializeByMemcpy
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Serialization/INetworkSerializeByMemcpy.cs.meta
Normal file
3
Runtime/Serialization/INetworkSerializeByMemcpy.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 11b763f46b18465cbffb1972d737a83e
|
||||||
|
timeCreated: 1647635592
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
public interface IReaderWriter
|
public interface IReaderWriter
|
||||||
@@ -9,17 +13,72 @@ namespace Unity.Netcode
|
|||||||
FastBufferWriter GetFastBufferWriter();
|
FastBufferWriter GetFastBufferWriter();
|
||||||
|
|
||||||
void SerializeValue(ref string s, bool oneByteChars = false);
|
void SerializeValue(ref string s, bool oneByteChars = false);
|
||||||
void SerializeValue<T>(ref T[] array) where T : unmanaged;
|
|
||||||
void SerializeValue(ref byte value);
|
void SerializeValue(ref byte value);
|
||||||
void SerializeValue<T>(ref T value) where T : unmanaged;
|
void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||||
|
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||||
|
void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||||
|
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||||
|
void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||||
|
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||||
|
void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||||
|
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||||
|
void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||||
|
void SerializeValue(ref Vector2 value);
|
||||||
|
void SerializeValue(ref Vector2[] value);
|
||||||
|
void SerializeValue(ref Vector3 value);
|
||||||
|
void SerializeValue(ref Vector3[] value);
|
||||||
|
void SerializeValue(ref Vector2Int value);
|
||||||
|
void SerializeValue(ref Vector2Int[] value);
|
||||||
|
void SerializeValue(ref Vector3Int value);
|
||||||
|
void SerializeValue(ref Vector3Int[] value);
|
||||||
|
void SerializeValue(ref Vector4 value);
|
||||||
|
void SerializeValue(ref Vector4[] value);
|
||||||
|
void SerializeValue(ref Quaternion value);
|
||||||
|
void SerializeValue(ref Quaternion[] value);
|
||||||
|
void SerializeValue(ref Color value);
|
||||||
|
void SerializeValue(ref Color[] value);
|
||||||
|
void SerializeValue(ref Color32 value);
|
||||||
|
void SerializeValue(ref Color32[] value);
|
||||||
|
void SerializeValue(ref Ray value);
|
||||||
|
void SerializeValue(ref Ray[] value);
|
||||||
|
void SerializeValue(ref Ray2D value);
|
||||||
|
void SerializeValue(ref Ray2D[] value);
|
||||||
|
|
||||||
// Has to have a different name to avoid conflicting with "where T: unmananged"
|
// Has to have a different name to avoid conflicting with "where T: unmananged"
|
||||||
void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new();
|
void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new();
|
||||||
|
|
||||||
bool PreCheck(int amount);
|
bool PreCheck(int amount);
|
||||||
void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
|
void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
|
||||||
void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged;
|
|
||||||
void SerializeValuePreChecked(ref byte value);
|
void SerializeValuePreChecked(ref byte value);
|
||||||
void SerializeValuePreChecked<T>(ref T value) where T : unmanaged;
|
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||||
|
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||||
|
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||||
|
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||||
|
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||||
|
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||||
|
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||||
|
|
||||||
|
void SerializeValuePreChecked(ref Vector2 value);
|
||||||
|
void SerializeValuePreChecked(ref Vector2[] value);
|
||||||
|
void SerializeValuePreChecked(ref Vector3 value);
|
||||||
|
void SerializeValuePreChecked(ref Vector3[] value);
|
||||||
|
void SerializeValuePreChecked(ref Vector2Int value);
|
||||||
|
void SerializeValuePreChecked(ref Vector2Int[] value);
|
||||||
|
void SerializeValuePreChecked(ref Vector3Int value);
|
||||||
|
void SerializeValuePreChecked(ref Vector3Int[] value);
|
||||||
|
void SerializeValuePreChecked(ref Vector4 value);
|
||||||
|
void SerializeValuePreChecked(ref Vector4[] value);
|
||||||
|
void SerializeValuePreChecked(ref Quaternion value);
|
||||||
|
void SerializeValuePreChecked(ref Quaternion[] value);
|
||||||
|
void SerializeValuePreChecked(ref Color value);
|
||||||
|
void SerializeValuePreChecked(ref Color[] value);
|
||||||
|
void SerializeValuePreChecked(ref Color32 value);
|
||||||
|
void SerializeValuePreChecked(ref Color32[] value);
|
||||||
|
void SerializeValuePreChecked(ref Ray value);
|
||||||
|
void SerializeValuePreChecked(ref Ray[] value);
|
||||||
|
void SerializeValuePreChecked(ref Ray2D value);
|
||||||
|
void SerializeValuePreChecked(ref Ray2D[] value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace Unity.Netcode
|
|||||||
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
|
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
|
||||||
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
|
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
|
||||||
{
|
{
|
||||||
networkBehaviour = (T)GetInternal(this, null);
|
networkBehaviour = GetInternal(this, null) as T;
|
||||||
return networkBehaviour != null;
|
return networkBehaviour != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Unity.Collections;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
@@ -136,23 +135,6 @@ namespace Unity.Netcode
|
|||||||
return OwnershipToObjectsTable[clientId].Values.ToList();
|
return OwnershipToObjectsTable[clientId].Values.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private struct TriggerData
|
|
||||||
{
|
|
||||||
public FastBufferReader Reader;
|
|
||||||
public MessageHeader Header;
|
|
||||||
public ulong SenderId;
|
|
||||||
public float Timestamp;
|
|
||||||
public int SerializedHeaderSize;
|
|
||||||
}
|
|
||||||
private struct TriggerInfo
|
|
||||||
{
|
|
||||||
public float Expiry;
|
|
||||||
public NativeList<TriggerData> TriggerData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<ulong, TriggerInfo> m_Triggers = new Dictionary<ulong, TriggerInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the NetworkManager associated with this SpawnManager.
|
/// Gets the NetworkManager associated with this SpawnManager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -209,87 +191,6 @@ namespace Unity.Netcode
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
|
||||||
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
|
||||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
|
||||||
///
|
|
||||||
/// 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, ref NetworkContext context)
|
|
||||||
{
|
|
||||||
if (!m_Triggers.ContainsKey(networkObjectId))
|
|
||||||
{
|
|
||||||
m_Triggers[networkObjectId] = new TriggerInfo
|
|
||||||
{
|
|
||||||
Expiry = Time.realtimeSinceStartup + 1,
|
|
||||||
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Triggers[networkObjectId].TriggerData.Add(new TriggerData
|
|
||||||
{
|
|
||||||
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
|
|
||||||
Header = context.Header,
|
|
||||||
Timestamp = context.Timestamp,
|
|
||||||
SenderId = context.SenderId,
|
|
||||||
SerializedHeaderSize = context.SerializedHeaderSize
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 unsafe void CleanupStaleTriggers()
|
|
||||||
{
|
|
||||||
ulong* staleKeys = stackalloc ulong[m_Triggers.Count()];
|
|
||||||
int index = 0;
|
|
||||||
foreach (var kvp in m_Triggers)
|
|
||||||
{
|
|
||||||
if (kvp.Value.Expiry < Time.realtimeSinceStartup)
|
|
||||||
{
|
|
||||||
|
|
||||||
staleKeys[index++] = kvp.Key;
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
||||||
{
|
|
||||||
NetworkLog.LogWarning($"Deferred messages were received for {nameof(NetworkObject)} #{kvp.Key}, but it did not spawn within 1 second.");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var data in kvp.Value.TriggerData)
|
|
||||||
{
|
|
||||||
data.Reader.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
kvp.Value.TriggerData.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < index; ++i)
|
|
||||||
{
|
|
||||||
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)
|
internal void RemoveOwnership(NetworkObject networkObject)
|
||||||
{
|
{
|
||||||
if (!NetworkManager.IsServer)
|
if (!NetworkManager.IsServer)
|
||||||
@@ -382,10 +283,37 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
||||||
|
{
|
||||||
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
|
||||||
|
{
|
||||||
|
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab))
|
||||||
|
{
|
||||||
|
switch (networkPrefab.Override)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case NetworkPrefabOverride.None:
|
||||||
|
return networkPrefab.Prefab != null;
|
||||||
|
case NetworkPrefabOverride.Hash:
|
||||||
|
case NetworkPrefabOverride.Prefab:
|
||||||
|
return networkPrefab.OverridingTargetPrefab != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle);
|
||||||
|
return networkObject != null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should only run on the client
|
/// Should only run on the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, int? networkSceneHandle, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
||||||
{
|
{
|
||||||
NetworkObject parentNetworkObject = null;
|
NetworkObject parentNetworkObject = null;
|
||||||
|
|
||||||
@@ -476,7 +404,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash);
|
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, networkSceneHandle);
|
||||||
|
|
||||||
if (networkObject == null)
|
if (networkObject == null)
|
||||||
{
|
{
|
||||||
@@ -549,15 +477,23 @@ namespace Unity.Netcode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this initialization really should be at the bottom of the function
|
|
||||||
networkObject.IsSpawned = true;
|
networkObject.IsSpawned = true;
|
||||||
|
|
||||||
// this initialization really should be at the top of this function. If and when we break the
|
|
||||||
// NetworkVariable dependency on NetworkBehaviour, this otherwise creates problems because
|
|
||||||
// SetNetworkVariableData above calls InitializeVariables, and the 'baked out' data isn't ready there;
|
|
||||||
// the current design banks on getting the network behaviour set and then only reading from it after the
|
|
||||||
// below initialization code. However cowardice compels me to hold off on moving this until that commit
|
|
||||||
networkObject.IsSceneObject = sceneObject;
|
networkObject.IsSceneObject = sceneObject;
|
||||||
|
|
||||||
|
// Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects
|
||||||
|
// Note: Always check SceneOriginHandle directly at this specific location.
|
||||||
|
if (networkObject.IsSceneObject != false && networkObject.SceneOriginHandle == 0)
|
||||||
|
{
|
||||||
|
networkObject.SceneOrigin = networkObject.gameObject.scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For integration testing, this makes sure that the appropriate NetworkManager is assigned to
|
||||||
|
// the NetworkObject since it uses the NetworkManager.Singleton when not set
|
||||||
|
if (networkObject.NetworkManagerOwner != NetworkManager)
|
||||||
|
{
|
||||||
|
networkObject.NetworkManagerOwner = NetworkManager;
|
||||||
|
}
|
||||||
|
|
||||||
networkObject.NetworkObjectId = networkId;
|
networkObject.NetworkObjectId = networkId;
|
||||||
|
|
||||||
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
|
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
|
||||||
@@ -612,20 +548,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
networkObject.InvokeBehaviourNetworkSpawn();
|
networkObject.InvokeBehaviourNetworkSpawn();
|
||||||
|
|
||||||
// This must happen after InvokeBehaviourNetworkSpawn, otherwise ClientRPCs and other messages can be
|
NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredMessageManager.TriggerType.OnSpawn, networkId);
|
||||||
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
|
||||||
if (m_Triggers.ContainsKey(networkId))
|
|
||||||
{
|
|
||||||
var triggerInfo = m_Triggers[networkId];
|
|
||||||
foreach (var trigger in triggerInfo.TriggerData)
|
|
||||||
{
|
|
||||||
// Reader will be disposed within HandleMessage
|
|
||||||
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
|
// propagate the IsSceneObject setting to child NetworkObjects
|
||||||
var children = networkObject.GetComponentsInChildren<NetworkObject>();
|
var children = networkObject.GetComponentsInChildren<NetworkObject>();
|
||||||
@@ -637,9 +560,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
||||||
{
|
{
|
||||||
//Currently, if this is called and the clientId (destination) is the server's client Id, this case will be checked
|
// If we are a host and sending to the host's client id, then we can skip sending ourselves the spawn message.
|
||||||
// within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer placing this check here. [NSS]
|
if (clientId == NetworkManager.ServerClientId)
|
||||||
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -870,7 +792,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
var message = new DestroyObjectMessage
|
var message = new DestroyObjectMessage
|
||||||
{
|
{
|
||||||
NetworkObjectId = networkObject.NetworkObjectId
|
NetworkObjectId = networkObject.NetworkObjectId,
|
||||||
|
DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true
|
||||||
};
|
};
|
||||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
||||||
foreach (var targetClientId in m_TargetClientIds)
|
foreach (var targetClientId in m_TargetClientIds)
|
||||||
@@ -888,6 +811,9 @@ namespace Unity.Netcode
|
|||||||
SpawnedObjectsList.Remove(networkObject);
|
SpawnedObjectsList.Remove(networkObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always clear out the observers list when despawned
|
||||||
|
networkObject.Observers.Clear();
|
||||||
|
|
||||||
var gobj = networkObject.gameObject;
|
var gobj = networkObject.gameObject;
|
||||||
if (destroyGameObject && gobj != null)
|
if (destroyGameObject && gobj != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -81,7 +81,10 @@ namespace Unity.Netcode
|
|||||||
: this(tickRate)
|
: this(tickRate)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(tickOffset < 1d / tickRate);
|
Assert.IsTrue(tickOffset < 1d / tickRate);
|
||||||
this += tick * m_TickInterval + tickOffset;
|
|
||||||
|
m_CachedTickOffset = tickOffset;
|
||||||
|
m_CachedTick = tick;
|
||||||
|
m_TimeSec = tick * m_TickInterval + tickOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Disconnect,
|
Disconnect,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transport has encountered an unrecoverable failure
|
||||||
|
/// </summary>
|
||||||
|
TransportFailure,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No new event
|
/// No new event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ namespace Unity.Netcode.Transports.UNET
|
|||||||
public override bool StartServer()
|
public override bool StartServer()
|
||||||
{
|
{
|
||||||
var topology = new HostTopology(GetConfig(), MaxConnections);
|
var topology = new HostTopology(GetConfig(), MaxConnections);
|
||||||
UnityEngine.Networking.NetworkTransport.AddHost(topology, ServerListenPort, null);
|
// Undocumented, but AddHost returns -1 in case of any type of failure. See UNET::NetLibraryManager::AddHost
|
||||||
return true;
|
return -1 != UnityEngine.Networking.NetworkTransport.AddHost(topology, ServerListenPort, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DisconnectRemoteClient(ulong clientId)
|
public override void DisconnectRemoteClient(ulong clientId)
|
||||||
|
|||||||
@@ -243,6 +243,15 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
PacketDropRate = 0
|
PacketDropRate = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private struct PacketLossCache
|
||||||
|
{
|
||||||
|
public int PacketsReceived;
|
||||||
|
public int PacketsDropped;
|
||||||
|
public float PacketLoss;
|
||||||
|
};
|
||||||
|
|
||||||
|
private PacketLossCache m_PacketLossCache = new PacketLossCache();
|
||||||
|
|
||||||
private State m_State = State.Disconnected;
|
private State m_State = State.Disconnected;
|
||||||
private NetworkDriver m_Driver;
|
private NetworkDriver m_Driver;
|
||||||
private NetworkSettings m_NetworkSettings;
|
private NetworkSettings m_NetworkSettings;
|
||||||
@@ -713,6 +722,15 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
|
|
||||||
m_Driver.ScheduleUpdate().Complete();
|
m_Driver.ScheduleUpdate().Complete();
|
||||||
|
|
||||||
|
if (m_ProtocolType == ProtocolType.RelayUnityTransport && m_Driver.GetRelayConnectionStatus() == RelayConnectionStatus.AllocationInvalid)
|
||||||
|
{
|
||||||
|
Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " +
|
||||||
|
"Use NetworkManager.OnTransportFailure to be notified of such events programmatically.");
|
||||||
|
|
||||||
|
InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, Time.realtimeSinceStartup);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (AcceptConnection() && m_Driver.IsCreated)
|
while (AcceptConnection() && m_Driver.IsCreated)
|
||||||
{
|
{
|
||||||
;
|
;
|
||||||
@@ -839,11 +857,22 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
{
|
{
|
||||||
var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr();
|
var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr();
|
||||||
|
|
||||||
var packetReceived = (float)sharedContext->stats.PacketsReceived;
|
var packetReceivedDelta = (float)(sharedContext->stats.PacketsReceived - m_PacketLossCache.PacketsReceived);
|
||||||
var packetDropped = (float)sharedContext->stats.PacketsDropped;
|
var packetDroppedDelta = (float)(sharedContext->stats.PacketsDropped - m_PacketLossCache.PacketsDropped);
|
||||||
var packetLoss = packetReceived > 0 ? packetDropped / packetReceived : 0;
|
|
||||||
|
|
||||||
return packetLoss;
|
// There can be multiple update happening in a single frame where no packets have transitioned
|
||||||
|
// In those situation we want to return the last packet loss value instead of 0 to avoid invalid swings
|
||||||
|
if (packetDroppedDelta == 0 && packetReceivedDelta == 0)
|
||||||
|
{
|
||||||
|
return m_PacketLossCache.PacketLoss;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_PacketLossCache.PacketsReceived = sharedContext->stats.PacketsReceived;
|
||||||
|
m_PacketLossCache.PacketsDropped = sharedContext->stats.PacketsDropped;
|
||||||
|
|
||||||
|
m_PacketLossCache.PacketLoss = packetReceivedDelta > 0 ? packetDroppedDelta / packetReceivedDelta : 0;
|
||||||
|
|
||||||
|
return m_PacketLossCache.PacketLoss;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1047,7 +1076,12 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClientBindAndConnect();
|
var succeeded = ClientBindAndConnect();
|
||||||
|
if (!succeeded)
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool StartServer()
|
public override bool StartServer()
|
||||||
@@ -1057,12 +1091,23 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool succeeded;
|
||||||
switch (m_ProtocolType)
|
switch (m_ProtocolType)
|
||||||
{
|
{
|
||||||
case ProtocolType.UnityTransport:
|
case ProtocolType.UnityTransport:
|
||||||
return ServerBindAndListen(ConnectionData.ListenEndPoint);
|
succeeded = ServerBindAndListen(ConnectionData.ListenEndPoint);
|
||||||
|
if (!succeeded)
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
return succeeded;
|
||||||
case ProtocolType.RelayUnityTransport:
|
case ProtocolType.RelayUnityTransport:
|
||||||
return StartRelayServer();
|
succeeded = StartRelayServer();
|
||||||
|
if (!succeeded)
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
return succeeded;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1090,6 +1135,8 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
|
|
||||||
DisposeInternals();
|
DisposeInternals();
|
||||||
|
|
||||||
|
m_ReliableReceiveQueues.Clear();
|
||||||
|
|
||||||
// We must reset this to zero because UTP actually re-uses clientIds if there is a clean disconnect
|
// We must reset this to zero because UTP actually re-uses clientIds if there is a clean disconnect
|
||||||
m_ServerClientId = 0;
|
m_ServerClientId = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,8 +152,8 @@ MonoBehaviour:
|
|||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3}
|
m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_ProtocolType: 0
|
m_ProtocolType: 0
|
||||||
m_MessageBufferSize: 6144
|
m_MessageBufferSize: 6144
|
||||||
m_ReciveQueueSize: 128
|
m_ReciveQueueSize: 128
|
||||||
@@ -171,8 +171,8 @@ MonoBehaviour:
|
|||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
|
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
DontDestroy: 1
|
DontDestroy: 1
|
||||||
RunInBackground: 1
|
RunInBackground: 1
|
||||||
LogLevel: 1
|
LogLevel: 1
|
||||||
@@ -185,7 +185,7 @@ MonoBehaviour:
|
|||||||
TickRate: 30
|
TickRate: 30
|
||||||
ClientConnectionBufferTimeout: 10
|
ClientConnectionBufferTimeout: 10
|
||||||
ConnectionApproval: 0
|
ConnectionApproval: 0
|
||||||
ConnectionData:
|
ConnectionData:
|
||||||
EnableTimeResync: 0
|
EnableTimeResync: 0
|
||||||
TimeResyncInterval: 30
|
TimeResyncInterval: 30
|
||||||
EnsureNetworkVariableLengthSafety: 0
|
EnsureNetworkVariableLengthSafety: 0
|
||||||
@@ -195,7 +195,7 @@ MonoBehaviour:
|
|||||||
NetworkIdRecycleDelay: 120
|
NetworkIdRecycleDelay: 120
|
||||||
RpcHashSize: 0
|
RpcHashSize: 0
|
||||||
LoadSceneTimeOut: 120
|
LoadSceneTimeOut: 120
|
||||||
MessageBufferTimeout: 20
|
SpawnTimeout: 1
|
||||||
EnableNetworkLogs: 1
|
EnableNetworkLogs: 1
|
||||||
--- !u!114 &1114774668
|
--- !u!114 &1114774668
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
@@ -207,8 +207,8 @@ MonoBehaviour:
|
|||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: 5fed568ebf6c14b11928f16219b5675b, type: 3}
|
m_Script: {fileID: 11500000, guid: 5fed568ebf6c14b11928f16219b5675b, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
--- !u!4 &1114774669
|
--- !u!4 &1114774669
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 3106ae882c6ec416d855a44c97eeaeef
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"displayName": "ClientNetworkTransform",
|
|
||||||
"description": "A sample to demonstrate how client-driven NetworkTransform can be implemented"
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 6b1ef235ca94b4bbd9a6456f44c69188
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 03def738b58f746408d456f1f8c99264
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 749af92bd75b44951b56ea583f3f10b5
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ClientNetworkTransform",
|
|
||||||
"rootNamespace": "Unity.Netcode.Samples",
|
|
||||||
"references": [
|
|
||||||
"Unity.Netcode.Runtime",
|
|
||||||
"Unity.Netcode.Components"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 78ac2a8d1365141f68da5d0a9e10dbc6
|
|
||||||
AssemblyDefinitionImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using Unity.Netcode.Components;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.Samples
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Used for syncing a transform with client side changes. This includes host. Pure server as owner isn't supported by this. Please use NetworkTransform
|
|
||||||
/// for transforms that'll always be owned by the server.
|
|
||||||
/// </summary>
|
|
||||||
[DisallowMultipleComponent]
|
|
||||||
public class ClientNetworkTransform : NetworkTransform
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Used to determine who can write to this transform. Owner client only.
|
|
||||||
/// Changing this value alone will not allow you to create a NetworkTransform which can be written to by clients.
|
|
||||||
/// We're using RPCs to send updated values from client to server. Netcode doesn't support client side network variable writing.
|
|
||||||
/// This imposes state to the server. This is putting trust on your clients. Make sure no security-sensitive features use this transform.
|
|
||||||
/// </summary>
|
|
||||||
// This is public to make sure that users don't depend on this IsClient && IsOwner check in their code. If this logic changes in the future, we can make it invisible here
|
|
||||||
|
|
||||||
public override void OnNetworkSpawn()
|
|
||||||
{
|
|
||||||
base.OnNetworkSpawn();
|
|
||||||
CanCommitToTransform = IsOwner;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
CanCommitToTransform = IsOwner;
|
|
||||||
base.Update();
|
|
||||||
if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
|
|
||||||
{
|
|
||||||
if (CanCommitToTransform)
|
|
||||||
{
|
|
||||||
TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
|
using System;
|
||||||
namespace Unity.Netcode.TestHelpers.Runtime
|
namespace Unity.Netcode.TestHelpers.Runtime
|
||||||
{
|
{
|
||||||
public class ObjectNameIdentifier : NetworkBehaviour
|
public class ObjectNameIdentifier : NetworkBehaviour
|
||||||
@@ -7,36 +7,46 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
private ulong m_CurrentNetworkObjectId;
|
private ulong m_CurrentNetworkObjectId;
|
||||||
private bool m_IsRegistered;
|
private bool m_IsRegistered;
|
||||||
|
|
||||||
|
private const char k_TagInfoStart = '{';
|
||||||
|
private const char k_TagInfoStop = '}';
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Keep a reference to the assigned NetworkObject
|
/// Keep a reference to the assigned NetworkObject
|
||||||
/// <see cref="OnDestroy"/>
|
/// <see cref="OnDestroy"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[NonSerialized]
|
||||||
private NetworkObject m_NetworkObject;
|
private NetworkObject m_NetworkObject;
|
||||||
|
private string m_OriginalName;
|
||||||
|
|
||||||
public override void OnNetworkSpawn()
|
public override void OnNetworkSpawn()
|
||||||
{
|
{
|
||||||
RegisterAndLabelNetworkObject();
|
RegisterAndLabelNetworkObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void RegisterAndLabelNetworkObject()
|
protected void RegisterAndLabelNetworkObject()
|
||||||
{
|
{
|
||||||
if (!m_IsRegistered)
|
if (!m_IsRegistered)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(m_OriginalName))
|
||||||
|
{
|
||||||
|
m_OriginalName = gameObject.name.Replace("(Clone)", "");
|
||||||
|
}
|
||||||
// This is required otherwise it will try to continue to update the NetworkBehaviour even if
|
// This is required otherwise it will try to continue to update the NetworkBehaviour even if
|
||||||
// it has been destroyed.
|
// it has been destroyed.
|
||||||
m_NetworkObject = NetworkObject;
|
m_NetworkObject = NetworkObject;
|
||||||
m_CurrentOwner = OwnerClientId;
|
m_CurrentOwner = OwnerClientId;
|
||||||
m_CurrentNetworkObjectId = NetworkObjectId;
|
m_CurrentNetworkObjectId = NetworkObjectId;
|
||||||
var objectOriginalName = gameObject.name.Replace("(Clone)", "");
|
|
||||||
var serverOrClient = IsServer ? "Server" : "Client";
|
var serverOrClient = IsServer ? "Server" : "Client";
|
||||||
if (NetworkObject.IsPlayerObject)
|
if (NetworkObject.IsPlayerObject)
|
||||||
{
|
{
|
||||||
gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{objectOriginalName}({OwnerClientId})-Local{objectOriginalName}" :
|
gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{m_OriginalName}-{k_TagInfoStart}{OwnerClientId}{k_TagInfoStop}-Local{m_OriginalName}" :
|
||||||
$"{objectOriginalName}({OwnerClientId})-On{serverOrClient}({NetworkManager.LocalClientId})";
|
$"{m_OriginalName}-{k_TagInfoStart}{OwnerClientId}{k_TagInfoStop}- On{serverOrClient}{k_TagInfoStart}{NetworkManager.LocalClientId}{k_TagInfoStop}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gameObject.name = $"{objectOriginalName}({NetworkObjectId})-On{serverOrClient}({NetworkManager.LocalClientId})";
|
gameObject.name = $"{m_OriginalName}{k_TagInfoStart}{NetworkObjectId}{k_TagInfoStop}-On{serverOrClient}{k_TagInfoStart}{NetworkManager.LocalClientId}{k_TagInfoStop}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't add the player objects to the global list of NetworkObjects
|
// Don't add the player objects to the global list of NetworkObjects
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
@@ -9,22 +10,51 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default SceneManagerHandler used for all NetcodeIntegrationTest derived children.
|
/// The default SceneManagerHandler used for all NetcodeIntegrationTest derived children.
|
||||||
|
/// This enables clients to load scenes within the same scene hierarchy during integration
|
||||||
|
/// testing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable
|
internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable
|
||||||
{
|
{
|
||||||
internal CoroutineRunner CoroutineRunner;
|
// All IntegrationTestSceneHandler instances register their associated NetworkManager
|
||||||
|
internal static List<NetworkManager> NetworkManagers = new List<NetworkManager>();
|
||||||
|
|
||||||
// Default client simulated delay time
|
internal static CoroutineRunner CoroutineRunner;
|
||||||
protected const float k_ClientLoadingSimulatedDelay = 0.02f;
|
|
||||||
|
internal static Queue<QueuedSceneJob> QueuedSceneJobs = new Queue<QueuedSceneJob>();
|
||||||
|
internal List<Coroutine> CoroutinesRunning = new List<Coroutine>();
|
||||||
|
internal static Coroutine SceneJobProcessor;
|
||||||
|
internal static QueuedSceneJob CurrentQueuedSceneJob;
|
||||||
|
protected static WaitForSeconds s_WaitForSeconds;
|
||||||
|
|
||||||
// Controls the client simulated delay time
|
|
||||||
protected float m_ClientLoadingSimulatedDelay = k_ClientLoadingSimulatedDelay;
|
|
||||||
|
|
||||||
public delegate bool CanClientsLoadUnloadDelegateHandler();
|
public delegate bool CanClientsLoadUnloadDelegateHandler();
|
||||||
public event CanClientsLoadUnloadDelegateHandler CanClientsLoad;
|
public static event CanClientsLoadUnloadDelegateHandler CanClientsLoad;
|
||||||
public event CanClientsLoadUnloadDelegateHandler CanClientsUnload;
|
public static event CanClientsLoadUnloadDelegateHandler CanClientsUnload;
|
||||||
|
|
||||||
internal List<Coroutine> CoroutinesRunning = new List<Coroutine>();
|
|
||||||
|
public static bool VerboseDebugMode;
|
||||||
|
/// <summary>
|
||||||
|
/// Used for loading scenes on the client-side during
|
||||||
|
/// an integration test
|
||||||
|
/// </summary>
|
||||||
|
internal class QueuedSceneJob
|
||||||
|
{
|
||||||
|
public enum JobTypes
|
||||||
|
{
|
||||||
|
Loading,
|
||||||
|
Unloading,
|
||||||
|
Completed
|
||||||
|
}
|
||||||
|
public JobTypes JobType;
|
||||||
|
public string SceneName;
|
||||||
|
public Scene Scene;
|
||||||
|
public ISceneManagerHandler.SceneEventAction SceneAction;
|
||||||
|
public IntegrationTestSceneHandler IntegrationTestSceneHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal NetworkManager NetworkManager;
|
||||||
|
|
||||||
|
internal string NetworkManagerName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to control when clients should attempt to fake-load a scene
|
/// Used to control when clients should attempt to fake-load a scene
|
||||||
@@ -44,19 +74,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
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()
|
protected bool OnCanClientsUnload()
|
||||||
{
|
{
|
||||||
if (CanClientsUnload != null)
|
if (CanClientsUnload != null)
|
||||||
@@ -66,35 +83,298 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fake-Unloads a scene for a client
|
internal static void VerboseDebug(string message)
|
||||||
/// </summary>
|
|
||||||
internal IEnumerator ClientUnloadSceneCoroutine(ISceneManagerHandler.SceneEventAction sceneEventAction)
|
|
||||||
{
|
{
|
||||||
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
|
if (VerboseDebugMode)
|
||||||
while (!OnCanClientsUnload())
|
|
||||||
{
|
{
|
||||||
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
|
Debug.Log(message);
|
||||||
}
|
}
|
||||||
sceneEventAction.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes scene loading jobs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queuedSceneJob">job to process</param>
|
||||||
|
static internal IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob)
|
||||||
|
{
|
||||||
|
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
||||||
|
while (!itegrationTestSceneHandler.OnCanClientsLoad())
|
||||||
|
{
|
||||||
|
yield return s_WaitForSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
|
||||||
|
// We always load additively for all scenes during integration tests
|
||||||
|
SceneManager.LoadSceneAsync(queuedSceneJob.SceneName, LoadSceneMode.Additive);
|
||||||
|
|
||||||
|
// Wait for it to finish
|
||||||
|
while (queuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed)
|
||||||
|
{
|
||||||
|
yield return s_WaitForSeconds;
|
||||||
|
}
|
||||||
|
yield return s_WaitForSeconds;
|
||||||
|
CurrentQueuedSceneJob.SceneAction.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles scene loading and assists with making sure the right NetworkManagerOwner
|
||||||
|
/// is assigned to newly instantiated NetworkObjects.
|
||||||
|
///
|
||||||
|
/// Note: Static property usage is OK since jobs are processed one at a time
|
||||||
|
/// </summary>
|
||||||
|
private static void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
|
||||||
|
{
|
||||||
|
if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.SceneName == scene.name)
|
||||||
|
{
|
||||||
|
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
||||||
|
|
||||||
|
ProcessInSceneObjects(scene, CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManager);
|
||||||
|
|
||||||
|
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles some pre-spawn processing of in-scene placed NetworkObjects
|
||||||
|
/// to make sure the appropriate NetworkManagerOwner is assigned. It
|
||||||
|
/// also makes sure that each in-scene placed NetworkObject has an
|
||||||
|
/// ObjectIdentifier component if one is not assigned to it or its
|
||||||
|
/// children.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scene">the scenes that was just loaded</param>
|
||||||
|
/// <param name="networkManager">the relative NetworkManager</param>
|
||||||
|
private static void ProcessInSceneObjects(Scene scene, NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
// Get all in-scene placed NeworkObjects that were instantiated when this scene loaded
|
||||||
|
var inSceneNetworkObjects = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != false && c.GetSceneOriginHandle() == scene.handle);
|
||||||
|
foreach (var sobj in inSceneNetworkObjects)
|
||||||
|
{
|
||||||
|
if (sobj.NetworkManagerOwner != networkManager)
|
||||||
|
{
|
||||||
|
sobj.NetworkManagerOwner = networkManager;
|
||||||
|
}
|
||||||
|
if (sobj.GetComponent<ObjectNameIdentifier>() == null && sobj.GetComponentInChildren<ObjectNameIdentifier>() == null)
|
||||||
|
{
|
||||||
|
sobj.gameObject.AddComponent<ObjectNameIdentifier>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes scene unloading jobs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queuedSceneJob">job to process</param>
|
||||||
|
static internal IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJob)
|
||||||
|
{
|
||||||
|
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
||||||
|
while (!itegrationTestSceneHandler.OnCanClientsUnload())
|
||||||
|
{
|
||||||
|
yield return s_WaitForSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneManager.sceneUnloaded += SceneManager_sceneUnloaded;
|
||||||
|
if (queuedSceneJob.Scene.IsValid() && queuedSceneJob.Scene.isLoaded && !queuedSceneJob.Scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName))
|
||||||
|
{
|
||||||
|
SceneManager.UnloadSceneAsync(queuedSceneJob.Scene);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for it to finish
|
||||||
|
while (queuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed)
|
||||||
|
{
|
||||||
|
yield return s_WaitForSeconds;
|
||||||
|
}
|
||||||
|
CurrentQueuedSceneJob.SceneAction.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles closing out scene unloading jobs
|
||||||
|
/// </summary>
|
||||||
|
private static void SceneManager_sceneUnloaded(Scene scene)
|
||||||
|
{
|
||||||
|
if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.Scene.name == scene.name)
|
||||||
|
{
|
||||||
|
SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
|
||||||
|
|
||||||
|
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes all jobs within the queue.
|
||||||
|
/// When all jobs are finished, the coroutine stops.
|
||||||
|
/// </summary>
|
||||||
|
static internal IEnumerator JobQueueProcessor()
|
||||||
|
{
|
||||||
|
while (QueuedSceneJobs.Count != 0)
|
||||||
|
{
|
||||||
|
CurrentQueuedSceneJob = QueuedSceneJobs.Dequeue();
|
||||||
|
VerboseDebug($"[ITSH-START] {CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManagerName} processing {CurrentQueuedSceneJob.JobType} for scene {CurrentQueuedSceneJob.SceneName}.");
|
||||||
|
if (CurrentQueuedSceneJob.JobType == QueuedSceneJob.JobTypes.Loading)
|
||||||
|
{
|
||||||
|
yield return ProcessLoadingSceneJob(CurrentQueuedSceneJob);
|
||||||
|
}
|
||||||
|
else if (CurrentQueuedSceneJob.JobType == QueuedSceneJob.JobTypes.Unloading)
|
||||||
|
{
|
||||||
|
yield return ProcessUnloadingSceneJob(CurrentQueuedSceneJob);
|
||||||
|
}
|
||||||
|
VerboseDebug($"[ITSH-STOP] {CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManagerName} processing {CurrentQueuedSceneJob.JobType} for scene {CurrentQueuedSceneJob.SceneName}.");
|
||||||
|
}
|
||||||
|
SceneJobProcessor = null;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a job to the job queue, and if the JobQueueProcessor coroutine
|
||||||
|
/// is not running then it will be started as well.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="queuedSceneJob">job to add to the queue</param>
|
||||||
|
private void AddJobToQueue(QueuedSceneJob queuedSceneJob)
|
||||||
|
{
|
||||||
|
QueuedSceneJobs.Enqueue(queuedSceneJob);
|
||||||
|
if (SceneJobProcessor == null)
|
||||||
|
{
|
||||||
|
SceneJobProcessor = CoroutineRunner.StartCoroutine(JobQueueProcessor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string m_ServerSceneBeingLoaded;
|
||||||
|
/// <summary>
|
||||||
|
/// Server always loads like it normally would
|
||||||
|
/// </summary>
|
||||||
|
public AsyncOperation GenericLoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||||
|
{
|
||||||
|
m_ServerSceneBeingLoaded = sceneName;
|
||||||
|
if (NetcodeIntegrationTest.IsRunning)
|
||||||
|
{
|
||||||
|
SceneManager.sceneLoaded += Sever_SceneLoaded;
|
||||||
|
}
|
||||||
|
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
|
||||||
|
|
||||||
|
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Sever_SceneLoaded(Scene scene, LoadSceneMode arg1)
|
||||||
|
{
|
||||||
|
if (m_ServerSceneBeingLoaded == scene.name)
|
||||||
|
{
|
||||||
|
ProcessInSceneObjects(scene, NetworkManager);
|
||||||
|
SceneManager.sceneLoaded -= Sever_SceneLoaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server always unloads like it normally would
|
||||||
|
/// </summary>
|
||||||
|
public AsyncOperation GenericUnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||||
|
{
|
||||||
|
var operation = SceneManager.UnloadSceneAsync(scene);
|
||||||
|
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||||
{
|
{
|
||||||
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientLoadSceneCoroutine(sceneName, sceneEventAction)));
|
// Server and non NetcodeIntegrationTest tests use the generic load scene method
|
||||||
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
|
if (!NetcodeIntegrationTest.IsRunning)
|
||||||
return new AsyncOperation();
|
{
|
||||||
|
return GenericLoadSceneAsync(sceneName, loadSceneMode, sceneEventAction);
|
||||||
|
}
|
||||||
|
else // NetcodeIntegrationTest Clients always get added to the jobs queue
|
||||||
|
{
|
||||||
|
AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, SceneName = sceneName, SceneAction = sceneEventAction, JobType = QueuedSceneJob.JobTypes.Loading });
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||||
{
|
{
|
||||||
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientUnloadSceneCoroutine(sceneEventAction)));
|
// Server and non NetcodeIntegrationTest tests use the generic unload scene method
|
||||||
|
if (!NetcodeIntegrationTest.IsRunning)
|
||||||
|
{
|
||||||
|
return GenericUnloadSceneAsync(scene, sceneEventAction);
|
||||||
|
}
|
||||||
|
else // NetcodeIntegrationTest Clients always get added to the jobs queue
|
||||||
|
{
|
||||||
|
AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene, SceneAction = sceneEventAction, JobType = QueuedSceneJob.JobTypes.Unloading });
|
||||||
|
}
|
||||||
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
|
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
|
||||||
return new AsyncOperation();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegrationTestSceneHandler()
|
/// <summary>
|
||||||
|
/// Replacement callback takes other NetworkManagers into consideration
|
||||||
|
/// </summary>
|
||||||
|
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
|
||||||
{
|
{
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
{
|
||||||
|
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||||
|
if (sceneLoaded.name == sceneName)
|
||||||
|
{
|
||||||
|
var skip = false;
|
||||||
|
foreach (var networkManager in NetworkManagers)
|
||||||
|
{
|
||||||
|
if (NetworkManager.LocalClientId == networkManager.LocalClientId || !networkManager.IsListening)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (networkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||||
|
{
|
||||||
|
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||||
|
{
|
||||||
|
NetworkLog.LogInfo($"{NetworkManager.name}'s ScenesLoaded contains {sceneLoaded.name} with a handle of {sceneLoaded.handle}. Skipping over scene.");
|
||||||
|
}
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip && !NetworkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||||
|
{
|
||||||
|
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||||
|
{
|
||||||
|
NetworkLog.LogInfo($"{NetworkManager.name} adding {sceneLoaded.name} with a handle of {sceneLoaded.handle} to its ScenesLoaded.");
|
||||||
|
}
|
||||||
|
NetworkManager.SceneManager.ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||||
|
return sceneLoaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ExcludeSceneFromSynchronizationCheck(Scene scene)
|
||||||
|
{
|
||||||
|
if (!NetworkManager.SceneManager.ScenesLoaded.ContainsKey(scene.handle) && SceneManager.GetActiveScene().handle != scene.handle)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor now must take NetworkManager
|
||||||
|
/// </summary>
|
||||||
|
public IntegrationTestSceneHandler(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
networkManager.SceneManager.OverrideGetAndAddNewlyLoadedSceneByName = GetAndAddNewlyLoadedSceneByName;
|
||||||
|
networkManager.SceneManager.ExcludeSceneFromSychronization = ExcludeSceneFromSynchronizationCheck;
|
||||||
|
NetworkManagers.Add(networkManager);
|
||||||
|
NetworkManagerName = networkManager.name;
|
||||||
|
if (s_WaitForSeconds == null)
|
||||||
|
{
|
||||||
|
s_WaitForSeconds = new WaitForSeconds(1.0f / networkManager.NetworkConfig.TickRate);
|
||||||
|
}
|
||||||
|
NetworkManager = networkManager;
|
||||||
if (CoroutineRunner == null)
|
if (CoroutineRunner == null)
|
||||||
{
|
{
|
||||||
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
|
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
|
||||||
@@ -103,12 +383,29 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var coroutine in CoroutinesRunning)
|
NetworkManagers.Clear();
|
||||||
|
if (SceneJobProcessor != null)
|
||||||
{
|
{
|
||||||
CoroutineRunner.StopCoroutine(coroutine);
|
CoroutineRunner.StopCoroutine(SceneJobProcessor);
|
||||||
|
SceneJobProcessor = null;
|
||||||
}
|
}
|
||||||
CoroutineRunner.StopAllCoroutines();
|
|
||||||
|
|
||||||
|
foreach (var job in QueuedSceneJobs)
|
||||||
|
{
|
||||||
|
if (job.JobType != QueuedSceneJob.JobTypes.Completed)
|
||||||
|
{
|
||||||
|
if (job.JobType == QueuedSceneJob.JobTypes.Loading)
|
||||||
|
{
|
||||||
|
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
|
||||||
|
}
|
||||||
|
job.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QueuedSceneJobs.Clear();
|
||||||
Object.Destroy(CoroutineRunner.gameObject);
|
Object.Destroy(CoroutineRunner.gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
@@ -16,7 +17,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class NetcodeIntegrationTest
|
public abstract class NetcodeIntegrationTest
|
||||||
{
|
{
|
||||||
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(4.0f);
|
/// <summary>
|
||||||
|
/// Used to determine if a NetcodeIntegrationTest is currently running to
|
||||||
|
/// determine how clients will load scenes
|
||||||
|
/// </summary>
|
||||||
|
internal static bool IsRunning { get; private set; }
|
||||||
|
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
|
||||||
protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
|
protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,11 +80,22 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int TotalClients => m_UseHost ? NumberOfClients + 1 : NumberOfClients;
|
protected int TotalClients => m_UseHost ? m_NumberOfClients + 1 : m_NumberOfClients;
|
||||||
|
|
||||||
protected const uint k_DefaultTickRate = 30;
|
protected const uint k_DefaultTickRate = 30;
|
||||||
|
|
||||||
|
private int m_NumberOfClients;
|
||||||
protected abstract int NumberOfClients { get; }
|
protected abstract int NumberOfClients { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set this to false to create the clients first.
|
||||||
|
/// Note: If you are using scene placed NetworkObjects or doing any form of scene testing and
|
||||||
|
/// get prefab hash id "soft synchronization" errors, then set this to false and run your test
|
||||||
|
/// again. This is a work-around until we can resolve some issues with NetworkManagerOwner and
|
||||||
|
/// NetworkManager.Singleton.
|
||||||
|
/// </summary>
|
||||||
|
protected bool m_CreateServerFirst = true;
|
||||||
|
|
||||||
public enum NetworkManagerInstatiationMode
|
public enum NetworkManagerInstatiationMode
|
||||||
{
|
{
|
||||||
PerTest, // This will create and destroy new NetworkManagers for each test within a child derived class
|
PerTest, // This will create and destroy new NetworkManagers for each test within a child derived class
|
||||||
@@ -108,11 +125,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
protected bool m_UseHost = true;
|
protected bool m_UseHost = true;
|
||||||
protected int m_TargetFrameRate = 60;
|
protected int m_TargetFrameRate = 60;
|
||||||
|
|
||||||
protected NetcodeIntegrationTestHelpers.InstanceTransport m_NetworkTransport = NetcodeIntegrationTestHelpers.InstanceTransport.SIP;
|
|
||||||
|
|
||||||
private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode;
|
private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode;
|
||||||
|
|
||||||
private bool m_EnableVerboseDebug;
|
protected bool m_EnableVerboseDebug { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to display the various integration test
|
/// Used to display the various integration test
|
||||||
@@ -158,8 +173,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
[OneTimeSetUp]
|
[OneTimeSetUp]
|
||||||
public void OneTimeSetup()
|
public void OneTimeSetup()
|
||||||
{
|
{
|
||||||
|
Application.runInBackground = true;
|
||||||
|
m_NumberOfClients = NumberOfClients;
|
||||||
|
IsRunning = true;
|
||||||
m_EnableVerboseDebug = OnSetVerboseDebug();
|
m_EnableVerboseDebug = OnSetVerboseDebug();
|
||||||
|
IntegrationTestSceneHandler.VerboseDebugMode = m_EnableVerboseDebug;
|
||||||
VerboseDebug($"Entering {nameof(OneTimeSetup)}");
|
VerboseDebug($"Entering {nameof(OneTimeSetup)}");
|
||||||
|
|
||||||
m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode();
|
m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode();
|
||||||
@@ -241,6 +259,61 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
CreateServerAndClients(NumberOfClients);
|
CreateServerAndClients(NumberOfClients);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnNewClientCreated(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetworkManager)
|
||||||
|
{
|
||||||
|
var clientNetworkManagersList = new List<NetworkManager>(m_ClientNetworkManagers);
|
||||||
|
if (addNetworkManager)
|
||||||
|
{
|
||||||
|
clientNetworkManagersList.Add(networkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clientNetworkManagersList.Remove(networkManager);
|
||||||
|
}
|
||||||
|
m_ClientNetworkManagers = clientNetworkManagersList.ToArray();
|
||||||
|
m_NumberOfClients = clientNetworkManagersList.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IEnumerator CreateAndStartNewClient()
|
||||||
|
{
|
||||||
|
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length);
|
||||||
|
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
|
||||||
|
|
||||||
|
// Notification that the new client (NetworkManager) has been created
|
||||||
|
// in the event any modifications need to be made before starting the client
|
||||||
|
OnNewClientCreated(networkManager);
|
||||||
|
|
||||||
|
NetcodeIntegrationTestHelpers.StartOneClient(networkManager);
|
||||||
|
AddRemoveNetworkManager(networkManager, true);
|
||||||
|
// Wait for the new client to connect
|
||||||
|
yield return WaitForClientsConnectedOrTimeOut();
|
||||||
|
if (s_GlobalTimeoutHelper.TimedOut)
|
||||||
|
{
|
||||||
|
AddRemoveNetworkManager(networkManager, false);
|
||||||
|
Object.Destroy(networkManager.gameObject);
|
||||||
|
}
|
||||||
|
AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!");
|
||||||
|
ClientNetworkManagerPostStart(networkManager);
|
||||||
|
VerboseDebug($"[{networkManager.name}] Created and connected!");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IEnumerator StopOneClient(NetworkManager networkManager, bool destroy = false)
|
||||||
|
{
|
||||||
|
NetcodeIntegrationTestHelpers.StopOneClient(networkManager, destroy);
|
||||||
|
AddRemoveNetworkManager(networkManager, false);
|
||||||
|
yield return WaitForConditionOrTimeOut(() => !networkManager.IsConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the server and clients
|
/// Creates the server and clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -252,7 +325,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
CreatePlayerPrefab();
|
CreatePlayerPrefab();
|
||||||
|
|
||||||
// Create multiple NetworkManager instances
|
// Create multiple NetworkManager instances
|
||||||
if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_NetworkTransport))
|
if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_CreateServerFirst))
|
||||||
{
|
{
|
||||||
Debug.LogError("Failed to create instances");
|
Debug.LogError("Failed to create instances");
|
||||||
Assert.Fail("Failed to create instances");
|
Assert.Fail("Failed to create instances");
|
||||||
@@ -308,6 +381,54 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClientNetworkManagerPostStart(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
networkManager.name = $"NetworkManager - Client - {networkManager.LocalClientId}";
|
||||||
|
Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!");
|
||||||
|
|
||||||
|
// Get all player instances for the current client NetworkManager instance
|
||||||
|
var clientPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId);
|
||||||
|
// Add this player instance to each client player entry
|
||||||
|
foreach (var playerNetworkObject in clientPlayerClones)
|
||||||
|
{
|
||||||
|
// When the server is not the host this needs to be done
|
||||||
|
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
||||||
|
{
|
||||||
|
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||||
|
}
|
||||||
|
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(networkManager.LocalClientId))
|
||||||
|
{
|
||||||
|
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ClientNetworkManagerPostStartInit()
|
||||||
|
{
|
||||||
|
// Creates a dictionary for all player instances client and server relative
|
||||||
|
// This provides a simpler way to get a specific player instance relative to a client instance
|
||||||
|
foreach (var networkManager in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
ClientNetworkManagerPostStart(networkManager);
|
||||||
|
}
|
||||||
|
if (m_UseHost)
|
||||||
|
{
|
||||||
|
var clientSideServerPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
|
||||||
|
foreach (var playerNetworkObject in clientSideServerPlayerClones)
|
||||||
|
{
|
||||||
|
// When the server is not the host this needs to be done
|
||||||
|
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
||||||
|
{
|
||||||
|
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||||
|
}
|
||||||
|
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(m_ServerNetworkManager.LocalClientId))
|
||||||
|
{
|
||||||
|
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This starts the server and clients as long as <see cref="CanStartServerAndClients"/>
|
/// This starts the server and clients as long as <see cref="CanStartServerAndClients"/>
|
||||||
/// returns true.
|
/// returns true.
|
||||||
@@ -333,8 +454,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
|
|
||||||
// Wait for all clients to connect
|
// Wait for all clients to connect
|
||||||
yield return WaitForClientsConnectedOrTimeOut();
|
yield return WaitForClientsConnectedOrTimeOut();
|
||||||
|
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
|
||||||
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
|
|
||||||
|
|
||||||
if (m_UseHost || m_ServerNetworkManager.IsHost)
|
if (m_UseHost || m_ServerNetworkManager.IsHost)
|
||||||
{
|
{
|
||||||
@@ -350,25 +470,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a dictionary for all player instances client and server relative
|
ClientNetworkManagerPostStartInit();
|
||||||
// This provides a simpler way to get a specific player instance relative to a client instance
|
|
||||||
foreach (var networkManager in m_ClientNetworkManagers)
|
|
||||||
{
|
|
||||||
Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!");
|
|
||||||
|
|
||||||
// Get all player instances for the current client NetworkManager instance
|
|
||||||
var clientPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId);
|
|
||||||
// Add this player instance to each client player entry
|
|
||||||
foreach (var playerNetworkObject in clientPlayerClones)
|
|
||||||
{
|
|
||||||
// When the server is not the host this needs to be done
|
|
||||||
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
|
||||||
{
|
|
||||||
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
|
||||||
}
|
|
||||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification that at this time the server and client(s) are instantiated,
|
// Notification that at this time the server and client(s) are instantiated,
|
||||||
// started, and connected on both sides.
|
// started, and connected on both sides.
|
||||||
@@ -402,11 +504,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected void DeRegisterSceneManagerHandler()
|
protected void DeRegisterSceneManagerHandler()
|
||||||
{
|
{
|
||||||
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
|
IntegrationTestSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
|
||||||
{
|
IntegrationTestSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
|
||||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
|
IntegrationTestSceneHandler.NetworkManagers.Clear();
|
||||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -416,11 +516,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected void RegisterSceneManagerHandler()
|
protected void RegisterSceneManagerHandler()
|
||||||
{
|
{
|
||||||
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
|
IntegrationTestSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
|
||||||
{
|
IntegrationTestSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
|
||||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
|
NetcodeIntegrationTestHelpers.RegisterSceneManagerHandler(m_ServerNetworkManager, true);
|
||||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ClientSceneHandler_CanClientsUnload()
|
private bool ClientSceneHandler_CanClientsUnload()
|
||||||
@@ -433,6 +531,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return CanClientsLoad();
|
return CanClientsLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected bool OnCanSceneCleanUpUnload(Scene scene)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This shuts down all NetworkManager instances registered via the
|
/// This shuts down all NetworkManager instances registered via the
|
||||||
/// <see cref="NetcodeIntegrationTestHelpers"/> class and cleans up
|
/// <see cref="NetcodeIntegrationTestHelpers"/> class and cleans up
|
||||||
@@ -445,11 +548,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// Shutdown and clean up both of our NetworkManager instances
|
// Shutdown and clean up both of our NetworkManager instances
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
|
DeRegisterSceneManagerHandler();
|
||||||
{
|
|
||||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
|
|
||||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetcodeIntegrationTestHelpers.Destroy();
|
NetcodeIntegrationTestHelpers.Destroy();
|
||||||
|
|
||||||
@@ -469,6 +568,8 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// Cleanup any remaining NetworkObjects
|
// Cleanup any remaining NetworkObjects
|
||||||
DestroySceneNetworkObjects();
|
DestroySceneNetworkObjects();
|
||||||
|
|
||||||
|
UnloadRemainingScenes();
|
||||||
|
|
||||||
// reset the m_ServerWaitForTick for the next test to initialize
|
// reset the m_ServerWaitForTick for the next test to initialize
|
||||||
s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
|
s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
|
||||||
VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}");
|
VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}");
|
||||||
@@ -510,6 +611,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
[OneTimeTearDown]
|
[OneTimeTearDown]
|
||||||
public void OneTimeTearDown()
|
public void OneTimeTearDown()
|
||||||
{
|
{
|
||||||
|
IntegrationTestSceneHandler.VerboseDebugMode = false;
|
||||||
VerboseDebug($"Entering {nameof(OneTimeTearDown)}");
|
VerboseDebug($"Entering {nameof(OneTimeTearDown)}");
|
||||||
OnOneTimeTearDown();
|
OnOneTimeTearDown();
|
||||||
|
|
||||||
@@ -521,7 +623,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// Disable NetcodeIntegrationTest auto-label feature
|
// Disable NetcodeIntegrationTest auto-label feature
|
||||||
NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(false);
|
NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(false);
|
||||||
|
|
||||||
|
UnloadRemainingScenes();
|
||||||
|
|
||||||
VerboseDebug($"Exiting {nameof(OneTimeTearDown)}");
|
VerboseDebug($"Exiting {nameof(OneTimeTearDown)}");
|
||||||
|
|
||||||
|
IsRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -558,6 +664,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
if (CanDestroyNetworkObject(networkObject))
|
if (CanDestroyNetworkObject(networkObject))
|
||||||
{
|
{
|
||||||
|
networkObject.NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
// Destroy the GameObject that holds the NetworkObject component
|
// Destroy the GameObject that holds the NetworkObject component
|
||||||
Object.DestroyImmediate(networkObject.gameObject);
|
Object.DestroyImmediate(networkObject.gameObject);
|
||||||
}
|
}
|
||||||
@@ -668,6 +775,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
var gameObject = new GameObject();
|
var gameObject = new GameObject();
|
||||||
gameObject.name = baseName;
|
gameObject.name = baseName;
|
||||||
var networkObject = gameObject.AddComponent<NetworkObject>();
|
var networkObject = gameObject.AddComponent<NetworkObject>();
|
||||||
|
networkObject.NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject);
|
NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject);
|
||||||
var networkPrefab = new NetworkPrefab() { Prefab = gameObject };
|
var networkPrefab = new NetworkPrefab() { Prefab = gameObject };
|
||||||
m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
|
m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
|
||||||
@@ -774,5 +882,32 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
m_UseHost = hostOrServer == HostOrServer.Host ? true : false;
|
m_UseHost = hostOrServer == HostOrServer.Host ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Just a helper function to avoid having to write the entire assert just to check if you
|
||||||
|
/// timed out.
|
||||||
|
/// </summary>
|
||||||
|
protected void AssertOnTimeout(string timeOutErrorMessage, TimeoutHelper assignedTimeoutHelper = null)
|
||||||
|
{
|
||||||
|
var timeoutHelper = assignedTimeoutHelper != null ? assignedTimeoutHelper : s_GlobalTimeoutHelper;
|
||||||
|
Assert.False(timeoutHelper.TimedOut, timeOutErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnloadRemainingScenes()
|
||||||
|
{
|
||||||
|
// Unload any remaining scenes loaded but the test runner scene
|
||||||
|
// Note: Some tests only unload the server-side instance, and this
|
||||||
|
// just assures no currently loaded scenes will impact the next test
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
{
|
||||||
|
var scene = SceneManager.GetSceneAt(i);
|
||||||
|
if (!scene.IsValid() || !scene.isLoaded || scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName) || !OnCanSceneCleanUpUnload(scene))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
VerboseDebug($"Unloading scene {scene.name}-{scene.handle}");
|
||||||
|
var asyncOperation = SceneManager.UnloadSceneAsync(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
public static class NetcodeIntegrationTestHelpers
|
public static class NetcodeIntegrationTestHelpers
|
||||||
{
|
{
|
||||||
public const int DefaultMinFrames = 1;
|
public const int DefaultMinFrames = 1;
|
||||||
public const float DefaultTimeout = 1f;
|
public const float DefaultTimeout = 4f;
|
||||||
private static List<NetworkManager> s_NetworkManagerInstances = new List<NetworkManager>();
|
private static List<NetworkManager> s_NetworkManagerInstances = new List<NetworkManager>();
|
||||||
private static Dictionary<NetworkManager, MultiInstanceHooks> s_Hooks = new Dictionary<NetworkManager, MultiInstanceHooks>();
|
private static Dictionary<NetworkManager, MultiInstanceHooks> s_Hooks = new Dictionary<NetworkManager, MultiInstanceHooks>();
|
||||||
private static bool s_IsStarted;
|
private static bool s_IsStarted;
|
||||||
@@ -30,10 +30,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
public MessageHandleCheck Check;
|
public MessageHandleCheck Check;
|
||||||
public bool Result;
|
public bool Result;
|
||||||
}
|
}
|
||||||
|
internal class MessageReceiveCheckWithResult
|
||||||
|
{
|
||||||
|
public Type CheckType;
|
||||||
|
public bool Result;
|
||||||
|
}
|
||||||
|
|
||||||
private class MultiInstanceHooks : INetworkHooks
|
private class MultiInstanceHooks : INetworkHooks
|
||||||
{
|
{
|
||||||
public Dictionary<Type, List<MessageHandleCheckWithResult>> HandleChecks = new Dictionary<Type, List<MessageHandleCheckWithResult>>();
|
public Dictionary<Type, List<MessageHandleCheckWithResult>> HandleChecks = new Dictionary<Type, List<MessageHandleCheckWithResult>>();
|
||||||
|
public List<MessageReceiveCheckWithResult> ReceiveChecks = new List<MessageReceiveCheckWithResult>();
|
||||||
|
|
||||||
public static bool CheckForMessageOfType<T>(object receivedMessage) where T : INetworkMessage
|
public static bool CheckForMessageOfType<T>(object receivedMessage) where T : INetworkMessage
|
||||||
{
|
{
|
||||||
@@ -50,6 +56,15 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
|
|
||||||
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||||
{
|
{
|
||||||
|
foreach (var check in ReceiveChecks)
|
||||||
|
{
|
||||||
|
if (check.CheckType == messageType)
|
||||||
|
{
|
||||||
|
check.Result = true;
|
||||||
|
ReceiveChecks.Remove(check);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||||
@@ -77,7 +92,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -103,31 +118,23 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string k_FirstPartOfTestRunnerSceneName = "InitTestScene";
|
internal const string FirstPartOfTestRunnerSceneName = "InitTestScene";
|
||||||
|
|
||||||
public static List<NetworkManager> NetworkManagerInstances => s_NetworkManagerInstances;
|
public static List<NetworkManager> NetworkManagerInstances => s_NetworkManagerInstances;
|
||||||
|
|
||||||
public enum InstanceTransport
|
internal static List<IntegrationTestSceneHandler> ClientSceneHandlers = new List<IntegrationTestSceneHandler>();
|
||||||
{
|
|
||||||
SIP,
|
|
||||||
UTP
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static IntegrationTestSceneHandler ClientSceneHandler = null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers the IntegrationTestSceneHandler for integration tests.
|
/// Registers the IntegrationTestSceneHandler for integration tests.
|
||||||
/// The default client behavior is to not load scenes on the client side.
|
/// The default client behavior is to not load scenes on the client side.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void RegisterSceneManagerHandler(NetworkManager networkManager)
|
internal static void RegisterSceneManagerHandler(NetworkManager networkManager, bool allowServer = false)
|
||||||
{
|
{
|
||||||
if (!networkManager.IsServer)
|
if (!networkManager.IsServer || networkManager.IsServer && allowServer)
|
||||||
{
|
{
|
||||||
if (ClientSceneHandler == null)
|
var handler = new IntegrationTestSceneHandler(networkManager);
|
||||||
{
|
ClientSceneHandlers.Add(handler);
|
||||||
ClientSceneHandler = new IntegrationTestSceneHandler();
|
networkManager.SceneManager.SceneManagerHandler = handler;
|
||||||
}
|
|
||||||
networkManager.SceneManager.SceneManagerHandler = ClientSceneHandler;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,11 +146,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void CleanUpHandlers()
|
public static void CleanUpHandlers()
|
||||||
{
|
{
|
||||||
if (ClientSceneHandler != null)
|
foreach (var handler in ClientSceneHandlers)
|
||||||
{
|
{
|
||||||
ClientSceneHandler.Dispose();
|
handler.Dispose();
|
||||||
ClientSceneHandler = null;
|
|
||||||
}
|
}
|
||||||
|
ClientSceneHandlers.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -162,20 +169,36 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static void AddUnityTransport(NetworkManager networkManager)
|
||||||
/// Create the correct NetworkTransport, attach it to the game object and return it.
|
|
||||||
/// Default value is SIPTransport.
|
|
||||||
/// </summary>
|
|
||||||
internal static NetworkTransport CreateInstanceTransport(InstanceTransport instanceTransport, GameObject go)
|
|
||||||
{
|
{
|
||||||
switch (instanceTransport)
|
// Create transport
|
||||||
|
var unityTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
||||||
|
// We need to increase this buffer size for tests that spawn a bunch of things
|
||||||
|
unityTransport.MaxPayloadSize = 256000;
|
||||||
|
unityTransport.MaxSendQueueSize = 1024 * 1024;
|
||||||
|
|
||||||
|
// Allow 4 connection attempts that each will time out after 500ms
|
||||||
|
unityTransport.MaxConnectAttempts = 4;
|
||||||
|
unityTransport.ConnectTimeoutMS = 500;
|
||||||
|
|
||||||
|
// Set the NetworkConfig
|
||||||
|
networkManager.NetworkConfig = new NetworkConfig()
|
||||||
{
|
{
|
||||||
case InstanceTransport.SIP:
|
// Set transport
|
||||||
return go.AddComponent<SIPTransport>();
|
NetworkTransport = unityTransport
|
||||||
default:
|
};
|
||||||
case InstanceTransport.UTP:
|
}
|
||||||
return go.AddComponent<UnityTransport>();
|
|
||||||
}
|
public static NetworkManager CreateServer()
|
||||||
|
{
|
||||||
|
// Create gameObject
|
||||||
|
var go = new GameObject("NetworkManager - Server");
|
||||||
|
|
||||||
|
// Create networkManager component
|
||||||
|
var server = go.AddComponent<NetworkManager>();
|
||||||
|
NetworkManagerInstances.Insert(0, server);
|
||||||
|
AddUnityTransport(server);
|
||||||
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -185,24 +208,22 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// <param name="server">The server NetworkManager</param>
|
/// <param name="server">The server NetworkManager</param>
|
||||||
/// <param name="clients">The clients NetworkManagers</param>
|
/// <param name="clients">The clients NetworkManagers</param>
|
||||||
/// <param name="targetFrameRate">The targetFrameRate of the Unity engine to use while the multi instance helper is running. Will be reset on shutdown.</param>
|
/// <param name="targetFrameRate">The targetFrameRate of the Unity engine to use while the multi instance helper is running. Will be reset on shutdown.</param>
|
||||||
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60, InstanceTransport instanceTransport = InstanceTransport.SIP)
|
/// <param name="serverFirst">This determines if the server or clients will be instantiated first (defaults to server first)</param>
|
||||||
|
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60, bool serverFirst = true)
|
||||||
{
|
{
|
||||||
s_NetworkManagerInstances = new List<NetworkManager>();
|
s_NetworkManagerInstances = new List<NetworkManager>();
|
||||||
CreateNewClients(clientCount, out clients, instanceTransport);
|
server = null;
|
||||||
|
if (serverFirst)
|
||||||
// Create gameObject
|
|
||||||
var go = new GameObject("NetworkManager - Server");
|
|
||||||
|
|
||||||
// Create networkManager component
|
|
||||||
server = go.AddComponent<NetworkManager>();
|
|
||||||
NetworkManagerInstances.Insert(0, server);
|
|
||||||
|
|
||||||
// Set the NetworkConfig
|
|
||||||
server.NetworkConfig = new NetworkConfig()
|
|
||||||
{
|
{
|
||||||
// Set transport
|
server = CreateServer();
|
||||||
NetworkTransport = CreateInstanceTransport(instanceTransport, go)
|
}
|
||||||
};
|
|
||||||
|
CreateNewClients(clientCount, out clients);
|
||||||
|
|
||||||
|
if (!serverFirst)
|
||||||
|
{
|
||||||
|
server = CreateServer();
|
||||||
|
}
|
||||||
|
|
||||||
s_OriginalTargetFrameRate = Application.targetFrameRate;
|
s_OriginalTargetFrameRate = Application.targetFrameRate;
|
||||||
Application.targetFrameRate = targetFrameRate;
|
Application.targetFrameRate = targetFrameRate;
|
||||||
@@ -210,28 +231,29 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static NetworkManager CreateNewClient(int identifier)
|
||||||
|
{
|
||||||
|
// Create gameObject
|
||||||
|
var go = new GameObject("NetworkManager - Client - " + identifier);
|
||||||
|
// Create networkManager component
|
||||||
|
var networkManager = go.AddComponent<NetworkManager>();
|
||||||
|
AddUnityTransport(networkManager);
|
||||||
|
return networkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to add a client to the already existing list of clients
|
/// Used to add a client to the already existing list of clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientCount">The amount of clients</param>
|
/// <param name="clientCount">The amount of clients</param>
|
||||||
/// <param name="clients"></param>
|
/// <param name="clients"></param>
|
||||||
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients, InstanceTransport instanceTransport = InstanceTransport.SIP)
|
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients)
|
||||||
{
|
{
|
||||||
clients = new NetworkManager[clientCount];
|
clients = new NetworkManager[clientCount];
|
||||||
var activeSceneName = SceneManager.GetActiveScene().name;
|
|
||||||
for (int i = 0; i < clientCount; i++)
|
for (int i = 0; i < clientCount; i++)
|
||||||
{
|
{
|
||||||
// Create gameObject
|
|
||||||
var go = new GameObject("NetworkManager - Client - " + i);
|
|
||||||
// Create networkManager component
|
// Create networkManager component
|
||||||
clients[i] = go.AddComponent<NetworkManager>();
|
clients[i] = CreateNewClient(i);
|
||||||
|
|
||||||
// Set the NetworkConfig
|
|
||||||
clients[i].NetworkConfig = new NetworkConfig()
|
|
||||||
{
|
|
||||||
// Set transport
|
|
||||||
NetworkTransport = CreateInstanceTransport(instanceTransport, go)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkManagerInstances.AddRange(clients);
|
NetworkManagerInstances.AddRange(clients);
|
||||||
@@ -242,12 +264,32 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// Stops one single client and makes sure to cleanup any static variables in this helper
|
/// Stops one single client and makes sure to cleanup any static variables in this helper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientToStop"></param>
|
/// <param name="clientToStop"></param>
|
||||||
public static void StopOneClient(NetworkManager clientToStop)
|
public static void StopOneClient(NetworkManager clientToStop, bool destroy = true)
|
||||||
{
|
{
|
||||||
clientToStop.Shutdown();
|
clientToStop.Shutdown();
|
||||||
s_Hooks.Remove(clientToStop);
|
s_Hooks.Remove(clientToStop);
|
||||||
Object.Destroy(clientToStop.gameObject);
|
if (destroy)
|
||||||
NetworkManagerInstances.Remove(clientToStop);
|
{
|
||||||
|
Object.Destroy(clientToStop.gameObject);
|
||||||
|
NetworkManagerInstances.Remove(clientToStop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts one single client and makes sure to register the required hooks and handlers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientToStart"></param>
|
||||||
|
public static void StartOneClient(NetworkManager clientToStart)
|
||||||
|
{
|
||||||
|
clientToStart.StartClient();
|
||||||
|
s_Hooks[clientToStart] = new MultiInstanceHooks();
|
||||||
|
clientToStart.MessagingSystem.Hook(s_Hooks[clientToStart]);
|
||||||
|
if (!NetworkManagerInstances.Contains(clientToStart))
|
||||||
|
{
|
||||||
|
NetworkManagerInstances.Add(clientToStart);
|
||||||
|
}
|
||||||
|
// if set, then invoke this for the client
|
||||||
|
RegisterHandlers(clientToStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -273,7 +315,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// Destroy the network manager instances
|
// Destroy the network manager instances
|
||||||
foreach (var networkManager in NetworkManagerInstances)
|
foreach (var networkManager in NetworkManagerInstances)
|
||||||
{
|
{
|
||||||
Object.DestroyImmediate(networkManager.gameObject);
|
if (networkManager.gameObject != null)
|
||||||
|
{
|
||||||
|
Object.Destroy(networkManager.gameObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkManagerInstances.Clear();
|
NetworkManagerInstances.Clear();
|
||||||
@@ -290,7 +335,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
|
private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
|
||||||
{
|
{
|
||||||
// exclude test runner scene
|
// exclude test runner scene
|
||||||
if (sceneName.StartsWith(k_FirstPartOfTestRunnerSceneName))
|
if (sceneName.StartsWith(FirstPartOfTestRunnerSceneName))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -317,7 +362,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// warning about using the currently active scene
|
// warning about using the currently active scene
|
||||||
var scene = SceneManager.GetActiveScene();
|
var scene = SceneManager.GetActiveScene();
|
||||||
// As long as this is a test runner scene (or most likely a test runner scene)
|
// As long as this is a test runner scene (or most likely a test runner scene)
|
||||||
if (scene.name.StartsWith(k_FirstPartOfTestRunnerSceneName))
|
if (scene.name.StartsWith(FirstPartOfTestRunnerSceneName))
|
||||||
{
|
{
|
||||||
// Register the test runner scene just so we avoid another warning about not being able to find the
|
// Register the test runner scene just so we avoid another warning about not being able to find the
|
||||||
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
|
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
|
||||||
@@ -433,8 +478,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// this feature only works with NetcodeIntegrationTest derived classes
|
// this feature only works with NetcodeIntegrationTest derived classes
|
||||||
if (IsNetcodeIntegrationTestRunning)
|
if (IsNetcodeIntegrationTestRunning)
|
||||||
{
|
{
|
||||||
// Add the object identifier component
|
if (networkObject.GetComponent<ObjectNameIdentifier>() == null && networkObject.GetComponentInChildren<ObjectNameIdentifier>() == null)
|
||||||
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
|
{
|
||||||
|
// Add the object identifier component
|
||||||
|
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,7 +745,35 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
|
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
|
||||||
/// <param name="timeout">The max time in seconds to wait for</param>
|
/// <param name="timeout">The max time in seconds to wait for</param>
|
||||||
internal static IEnumerator WaitForMessageOfType<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = 0.5f) where T : INetworkMessage
|
internal static IEnumerator WaitForMessageOfTypeReceived<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = DefaultTimeout) where T : INetworkMessage
|
||||||
|
{
|
||||||
|
var hooks = s_Hooks[toBeReceivedBy];
|
||||||
|
var check = new MessageReceiveCheckWithResult { CheckType = typeof(T) };
|
||||||
|
hooks.ReceiveChecks.Add(check);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
result = new ResultWrapper<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime = Time.realtimeSinceStartup;
|
||||||
|
|
||||||
|
while (!check.Result && Time.realtimeSinceStartup - startTime < timeout)
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = check.Result;
|
||||||
|
result.Result = res;
|
||||||
|
|
||||||
|
Assert.True(result.Result, $"Expected message {typeof(T).Name} was not received within {timeout}s.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for a message of the given type to be received
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
|
||||||
|
/// <param name="timeout">The max time in seconds to wait for</param>
|
||||||
|
internal static IEnumerator WaitForMessageOfTypeHandled<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = DefaultTimeout) where T : INetworkMessage
|
||||||
{
|
{
|
||||||
var hooks = s_Hooks[toBeReceivedBy];
|
var hooks = s_Hooks[toBeReceivedBy];
|
||||||
if (!hooks.HandleChecks.ContainsKey(typeof(T)))
|
if (!hooks.HandleChecks.ContainsKey(typeof(T)))
|
||||||
@@ -712,7 +788,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
yield return ExecuteWaitForHook(check, result, timeout);
|
yield return ExecuteWaitForHook(check, result, timeout);
|
||||||
|
|
||||||
Assert.True(result.Result, $"Expected message {typeof(T).Name} was not received within {timeout}s.");
|
Assert.True(result.Result, $"Expected message {typeof(T).Name} was not handled within {timeout}s.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -721,7 +797,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// <param name="requirement">Called for each received message to check if it's the right one</param>
|
/// <param name="requirement">Called for each received message to check if it's the right one</param>
|
||||||
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
|
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
|
||||||
/// <param name="timeout">The max time in seconds to wait for</param>
|
/// <param name="timeout">The max time in seconds to wait for</param>
|
||||||
internal static IEnumerator WaitForMessageMeetingRequirement<T>(NetworkManager toBeReceivedBy, MessageHandleCheck requirement, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)
|
internal static IEnumerator WaitForMessageMeetingRequirementHandled<T>(NetworkManager toBeReceivedBy, MessageHandleCheck requirement, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)
|
||||||
{
|
{
|
||||||
var hooks = s_Hooks[toBeReceivedBy];
|
var hooks = s_Hooks[toBeReceivedBy];
|
||||||
if (!hooks.HandleChecks.ContainsKey(typeof(T)))
|
if (!hooks.HandleChecks.ContainsKey(typeof(T)))
|
||||||
@@ -736,7 +812,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
yield return ExecuteWaitForHook(check, result, timeout);
|
yield return ExecuteWaitForHook(check, result, timeout);
|
||||||
|
|
||||||
Assert.True(result.Result, $"Expected message meeting user requirements was not received within {timeout}s.");
|
Assert.True(result.Result, $"Expected message meeting user requirements was not handled within {timeout}s.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerator ExecuteWaitForHook(MessageHandleCheckWithResult check, ResultWrapper<bool> result, float timeout)
|
private static IEnumerator ExecuteWaitForHook(MessageHandleCheckWithResult check, ResultWrapper<bool> result, float timeout)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.Transports.UTP;
|
||||||
|
|
||||||
namespace Unity.Netcode.TestHelpers.Runtime
|
namespace Unity.Netcode.TestHelpers.Runtime
|
||||||
{
|
{
|
||||||
@@ -67,11 +68,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
|
|
||||||
Debug.Log($"{nameof(NetworkManager)} Instantiated.");
|
Debug.Log($"{nameof(NetworkManager)} Instantiated.");
|
||||||
|
|
||||||
// NOTE: For now we only use SIPTransport for tests until UnityTransport
|
var unityTransport = NetworkManagerGameObject.AddComponent<UnityTransport>();
|
||||||
// has been verified working in nightly builds
|
|
||||||
// TODO-MTT-2486: Provide support for other transports once tested and verified
|
|
||||||
// working on consoles.
|
|
||||||
var sipTransport = NetworkManagerGameObject.AddComponent<SIPTransport>();
|
|
||||||
if (networkConfig == null)
|
if (networkConfig == null)
|
||||||
{
|
{
|
||||||
networkConfig = new NetworkConfig
|
networkConfig = new NetworkConfig
|
||||||
@@ -81,7 +78,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
NetworkManagerObject.NetworkConfig = networkConfig;
|
NetworkManagerObject.NetworkConfig = networkConfig;
|
||||||
NetworkManagerObject.NetworkConfig.NetworkTransport = sipTransport;
|
NetworkManagerObject.NetworkConfig.NetworkTransport = unityTransport;
|
||||||
|
|
||||||
// Starts the network manager in the mode specified
|
// Starts the network manager in the mode specified
|
||||||
StartNetworkManagerMode(managerMode);
|
StartNetworkManagerMode(managerMode);
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: d764f651f0e54e8281952933cc49be97
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.TestHelpers.Runtime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SIPTransport (SIngleProcessTransport)
|
|
||||||
/// is a NetworkTransport designed to be used with multiple network instances in a single process
|
|
||||||
/// it's designed for the netcode in a way where no networking stack has to be available
|
|
||||||
/// it's designed for testing purposes and it's not designed with speed in mind
|
|
||||||
/// </summary>
|
|
||||||
public class SIPTransport : TestingNetworkTransport
|
|
||||||
{
|
|
||||||
private struct Event
|
|
||||||
{
|
|
||||||
public NetworkEvent Type;
|
|
||||||
public ulong ConnectionId;
|
|
||||||
public ArraySegment<byte> Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Peer
|
|
||||||
{
|
|
||||||
public ulong ConnectionId;
|
|
||||||
public SIPTransport Transport;
|
|
||||||
public Queue<Event> IncomingBuffer = new Queue<Event>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<ulong, Peer> m_Peers = new Dictionary<ulong, Peer>();
|
|
||||||
private ulong m_ClientsCounter = 1;
|
|
||||||
|
|
||||||
private static Peer s_Server;
|
|
||||||
private Peer m_LocalConnection;
|
|
||||||
|
|
||||||
public override ulong ServerClientId => 0;
|
|
||||||
public ulong LocalClientId;
|
|
||||||
|
|
||||||
public override void DisconnectLocalClient()
|
|
||||||
{
|
|
||||||
if (m_LocalConnection != null)
|
|
||||||
{
|
|
||||||
// Inject local disconnect
|
|
||||||
m_LocalConnection.IncomingBuffer.Enqueue(new Event
|
|
||||||
{
|
|
||||||
Type = NetworkEvent.Disconnect,
|
|
||||||
ConnectionId = m_LocalConnection.ConnectionId,
|
|
||||||
Data = new ArraySegment<byte>()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (s_Server != null && m_LocalConnection != null)
|
|
||||||
{
|
|
||||||
// Remove the connection
|
|
||||||
s_Server.Transport.m_Peers.Remove(m_LocalConnection.ConnectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_LocalConnection.ConnectionId == ServerClientId)
|
|
||||||
{
|
|
||||||
StopServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the local connection
|
|
||||||
m_LocalConnection = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by server
|
|
||||||
public override void DisconnectRemoteClient(ulong clientId)
|
|
||||||
{
|
|
||||||
if (m_Peers.ContainsKey(clientId))
|
|
||||||
{
|
|
||||||
// Inject disconnect into remote
|
|
||||||
m_Peers[clientId].IncomingBuffer.Enqueue(new Event
|
|
||||||
{
|
|
||||||
Type = NetworkEvent.Disconnect,
|
|
||||||
ConnectionId = clientId,
|
|
||||||
Data = new ArraySegment<byte>()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inject local disconnect
|
|
||||||
m_LocalConnection.IncomingBuffer.Enqueue(new Event
|
|
||||||
{
|
|
||||||
Type = NetworkEvent.Disconnect,
|
|
||||||
ConnectionId = clientId,
|
|
||||||
Data = new ArraySegment<byte>()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove the local connection on remote
|
|
||||||
m_Peers[clientId].Transport.m_LocalConnection = null;
|
|
||||||
|
|
||||||
// Remove connection on server
|
|
||||||
m_Peers.Remove(clientId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ulong GetCurrentRtt(ulong clientId)
|
|
||||||
{
|
|
||||||
// Always returns 50ms
|
|
||||||
return 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(NetworkManager networkManager = null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopServer()
|
|
||||||
{
|
|
||||||
s_Server = null;
|
|
||||||
m_Peers.Remove(ServerClientId);
|
|
||||||
m_LocalConnection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown()
|
|
||||||
{
|
|
||||||
// Inject disconnects to all the remotes
|
|
||||||
foreach (KeyValuePair<ulong, Peer> onePeer in m_Peers)
|
|
||||||
{
|
|
||||||
onePeer.Value.IncomingBuffer.Enqueue(new Event
|
|
||||||
{
|
|
||||||
Type = NetworkEvent.Disconnect,
|
|
||||||
ConnectionId = LocalClientId,
|
|
||||||
Data = new ArraySegment<byte>()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_LocalConnection != null && m_LocalConnection.ConnectionId == ServerClientId)
|
|
||||||
{
|
|
||||||
StopServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool StartClient()
|
|
||||||
{
|
|
||||||
if (s_Server == null)
|
|
||||||
{
|
|
||||||
// No server
|
|
||||||
Debug.LogError("No server");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_LocalConnection != null)
|
|
||||||
{
|
|
||||||
// Already connected
|
|
||||||
Debug.LogError("Already connected");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate an Id for the server that represents this client
|
|
||||||
ulong serverConnectionId = ++s_Server.Transport.m_ClientsCounter;
|
|
||||||
LocalClientId = serverConnectionId;
|
|
||||||
|
|
||||||
// Create local connection
|
|
||||||
m_LocalConnection = new Peer()
|
|
||||||
{
|
|
||||||
ConnectionId = serverConnectionId,
|
|
||||||
Transport = this,
|
|
||||||
IncomingBuffer = new Queue<Event>()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the server as a local connection
|
|
||||||
m_Peers.Add(ServerClientId, s_Server);
|
|
||||||
|
|
||||||
// Add local connection as a connection on the server
|
|
||||||
s_Server.Transport.m_Peers.Add(serverConnectionId, m_LocalConnection);
|
|
||||||
|
|
||||||
// Sends a connect message to the server
|
|
||||||
s_Server.Transport.m_LocalConnection.IncomingBuffer.Enqueue(new Event()
|
|
||||||
{
|
|
||||||
Type = NetworkEvent.Connect,
|
|
||||||
ConnectionId = serverConnectionId,
|
|
||||||
Data = new ArraySegment<byte>()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send a local connect message
|
|
||||||
m_LocalConnection.IncomingBuffer.Enqueue(new Event
|
|
||||||
{
|
|
||||||
Type = NetworkEvent.Connect,
|
|
||||||
ConnectionId = ServerClientId,
|
|
||||||
Data = new ArraySegment<byte>()
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool StartServer()
|
|
||||||
{
|
|
||||||
if (s_Server != null)
|
|
||||||
{
|
|
||||||
// Can only have one server
|
|
||||||
Debug.LogError("Server already started");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_LocalConnection != null)
|
|
||||||
{
|
|
||||||
// Already connected
|
|
||||||
Debug.LogError("Already connected");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create local connection
|
|
||||||
m_LocalConnection = new Peer()
|
|
||||||
{
|
|
||||||
ConnectionId = ServerClientId,
|
|
||||||
Transport = this,
|
|
||||||
IncomingBuffer = new Queue<Event>()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the local connection as the server
|
|
||||||
s_Server = m_LocalConnection;
|
|
||||||
|
|
||||||
m_Peers.Add(ServerClientId, s_Server);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery)
|
|
||||||
{
|
|
||||||
if (m_LocalConnection != null)
|
|
||||||
{
|
|
||||||
// Create copy since netcode wants the byte array back straight after the method call.
|
|
||||||
// Hard on GC.
|
|
||||||
byte[] copy = new byte[payload.Count];
|
|
||||||
Buffer.BlockCopy(payload.Array, payload.Offset, copy, 0, payload.Count);
|
|
||||||
|
|
||||||
if (m_Peers.ContainsKey(clientId))
|
|
||||||
{
|
|
||||||
m_Peers[clientId].IncomingBuffer.Enqueue(new Event
|
|
||||||
{
|
|
||||||
Type = NetworkEvent.Data,
|
|
||||||
ConnectionId = m_LocalConnection.ConnectionId,
|
|
||||||
Data = new ArraySegment<byte>(copy)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
|
|
||||||
{
|
|
||||||
if (m_LocalConnection != null)
|
|
||||||
{
|
|
||||||
if (m_LocalConnection.IncomingBuffer.Count == 0)
|
|
||||||
{
|
|
||||||
clientId = 0;
|
|
||||||
payload = new ArraySegment<byte>();
|
|
||||||
receiveTime = 0;
|
|
||||||
return NetworkEvent.Nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
var peerEvent = m_LocalConnection.IncomingBuffer.Dequeue();
|
|
||||||
|
|
||||||
clientId = peerEvent.ConnectionId;
|
|
||||||
payload = peerEvent.Data;
|
|
||||||
receiveTime = 0;
|
|
||||||
|
|
||||||
return peerEvent.Type;
|
|
||||||
}
|
|
||||||
|
|
||||||
clientId = 0;
|
|
||||||
payload = new ArraySegment<byte>();
|
|
||||||
receiveTime = 0;
|
|
||||||
return NetworkEvent.Nothing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1fd1b14eba874a189f13f12d343c331c
|
|
||||||
timeCreated: 1620145176
|
|
||||||
@@ -340,8 +340,8 @@ MonoBehaviour:
|
|||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
|
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
DontDestroy: 1
|
DontDestroy: 1
|
||||||
RunInBackground: 1
|
RunInBackground: 1
|
||||||
LogLevel: 1
|
LogLevel: 1
|
||||||
@@ -356,7 +356,7 @@ MonoBehaviour:
|
|||||||
TickRate: 30
|
TickRate: 30
|
||||||
ClientConnectionBufferTimeout: 10
|
ClientConnectionBufferTimeout: 10
|
||||||
ConnectionApproval: 0
|
ConnectionApproval: 0
|
||||||
ConnectionData:
|
ConnectionData:
|
||||||
EnableTimeResync: 0
|
EnableTimeResync: 0
|
||||||
TimeResyncInterval: 30
|
TimeResyncInterval: 30
|
||||||
EnableNetworkVariable: 1
|
EnableNetworkVariable: 1
|
||||||
@@ -367,5 +367,5 @@ MonoBehaviour:
|
|||||||
NetworkIdRecycleDelay: 120
|
NetworkIdRecycleDelay: 120
|
||||||
RpcHashSize: 0
|
RpcHashSize: 0
|
||||||
LoadSceneTimeOut: 120
|
LoadSceneTimeOut: 120
|
||||||
MessageBufferTimeout: 20
|
SpawnTimeout: 20
|
||||||
EnableNetworkLogs: 1
|
EnableNetworkLogs: 1
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using NUnit.Framework;
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Build.Reporting;
|
using UnityEditor.Build.Reporting;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.EditorTests
|
namespace Unity.Netcode.EditorTests
|
||||||
{
|
{
|
||||||
@@ -16,13 +17,25 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
var execAssembly = Assembly.GetExecutingAssembly();
|
var execAssembly = Assembly.GetExecutingAssembly();
|
||||||
var packagePath = UnityEditor.PackageManager.PackageInfo.FindForAssembly(execAssembly).assetPath;
|
var packagePath = UnityEditor.PackageManager.PackageInfo.FindForAssembly(execAssembly).assetPath;
|
||||||
|
var buildTarget = EditorUserBuildSettings.activeBuildTarget;
|
||||||
|
var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
|
||||||
|
var buildTargetSupported = BuildPipeline.IsBuildTargetSupported(buildTargetGroup, buildTarget);
|
||||||
|
|
||||||
var buildReport = BuildPipeline.BuildPlayer(
|
var buildReport = BuildPipeline.BuildPlayer(
|
||||||
new[] { Path.Combine(packagePath, DefaultBuildScenePath) },
|
new[] { Path.Combine(packagePath, DefaultBuildScenePath) },
|
||||||
Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)),
|
Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)),
|
||||||
EditorUserBuildSettings.activeBuildTarget,
|
buildTarget,
|
||||||
BuildOptions.None
|
BuildOptions.None
|
||||||
);
|
);
|
||||||
Assert.AreEqual(BuildResult.Succeeded, buildReport.summary.result);
|
|
||||||
|
if (buildTargetSupported)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(BuildResult.Succeeded, buildReport.summary.result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogAssert.Expect(LogType.Error, "Error building player because build target was unsupported");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
public class MessageReceivingTests
|
public class MessageReceivingTests
|
||||||
{
|
{
|
||||||
private struct TestMessage : INetworkMessage
|
private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public int A;
|
public int A;
|
||||||
public int B;
|
public int B;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
public class MessageRegistrationTests
|
public class MessageRegistrationTests
|
||||||
{
|
{
|
||||||
private struct TestMessageOne : INetworkMessage
|
private struct TestMessageOne : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public int A;
|
public int A;
|
||||||
public int B;
|
public int B;
|
||||||
@@ -25,7 +25,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct TestMessageTwo : INetworkMessage
|
private struct TestMessageTwo : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public int A;
|
public int A;
|
||||||
public int B;
|
public int B;
|
||||||
@@ -64,7 +64,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct TestMessageThree : INetworkMessage
|
private struct TestMessageThree : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public int A;
|
public int A;
|
||||||
public int B;
|
public int B;
|
||||||
@@ -97,7 +97,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private struct TestMessageFour : INetworkMessage
|
private struct TestMessageFour : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public int A;
|
public int A;
|
||||||
public int B;
|
public int B;
|
||||||
@@ -173,8 +173,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
MessagingSystem.MessageHandler handlerThree = MessagingSystem.ReceiveMessage<TestMessageThree>;
|
MessagingSystem.MessageHandler handlerThree = MessagingSystem.ReceiveMessage<TestMessageThree>;
|
||||||
MessagingSystem.MessageHandler handlerFour = MessagingSystem.ReceiveMessage<TestMessageFour>;
|
MessagingSystem.MessageHandler handlerFour = MessagingSystem.ReceiveMessage<TestMessageFour>;
|
||||||
|
|
||||||
var foundHandlerOne = systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))];
|
|
||||||
|
|
||||||
Assert.AreEqual(handlerOne, systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))]);
|
Assert.AreEqual(handlerOne, systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))]);
|
||||||
Assert.AreEqual(handlerTwo, systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageTwo))]);
|
Assert.AreEqual(handlerTwo, systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageTwo))]);
|
||||||
Assert.AreEqual(handlerThree, systemTwo.MessageHandlers[systemTwo.GetMessageType(typeof(TestMessageThree))]);
|
Assert.AreEqual(handlerThree, systemTwo.MessageHandlers[systemTwo.GetMessageType(typeof(TestMessageThree))]);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
public class MessageSendingTests
|
public class MessageSendingTests
|
||||||
{
|
{
|
||||||
private struct TestMessage : INetworkMessage
|
private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public int A;
|
public int A;
|
||||||
public int B;
|
public int B;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Unity.Netcode.Editor;
|
using Unity.Netcode.Editor;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.EditorTests
|
namespace Unity.Netcode.EditorTests
|
||||||
@@ -78,5 +80,37 @@ namespace Unity.Netcode.EditorTests
|
|||||||
// Clean up
|
// Clean up
|
||||||
Object.DestroyImmediate(gameObject);
|
Object.DestroyImmediate(gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NestedNetworkObjectPrefabCheck()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
||||||
|
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
||||||
|
networkManager.NetworkConfig = new NetworkConfig();
|
||||||
|
|
||||||
|
var parent = new GameObject("Parent").AddComponent<NetworkObject>();
|
||||||
|
var child = new GameObject("Child").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
|
// Set parent
|
||||||
|
child.transform.SetParent(parent.transform);
|
||||||
|
|
||||||
|
// Make it a prefab, warning only applies to prefabs
|
||||||
|
networkManager.AddNetworkPrefab(parent.gameObject);
|
||||||
|
|
||||||
|
// Mark scene as dirty to ensure OnValidate actually runs
|
||||||
|
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||||||
|
|
||||||
|
// Force OnValidate
|
||||||
|
networkManager.OnValidate();
|
||||||
|
|
||||||
|
// Expect a warning
|
||||||
|
LogAssert.Expect(LogType.Warning, $"[Netcode] {NetworkManager.PrefabDebugHelper(networkManager.NetworkConfig.NetworkPrefabs[0])} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)");
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
Object.DestroyImmediate(networkManagerObject);
|
||||||
|
Object.DestroyImmediate(parent);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
C
|
C
|
||||||
};
|
};
|
||||||
|
|
||||||
protected struct TestStruct
|
protected struct TestStruct : INetworkSerializeByMemcpy
|
||||||
{
|
{
|
||||||
public byte A;
|
public byte A;
|
||||||
public short B;
|
public short B;
|
||||||
@@ -80,7 +80,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
protected abstract void RunTypeTest<T>(T valueToTest) where T : unmanaged;
|
protected abstract void RunTypeTest<T>(T valueToTest) where T : unmanaged;
|
||||||
|
|
||||||
protected abstract void RunTypeTestSafe<T>(T valueToTest) where T : unmanaged;
|
protected abstract void RunTypeTestSafe<T>(T valueToTest) where T : unmanaged;
|
||||||
@@ -114,143 +113,152 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
private void RunTestWithWriteType<T>(T val, WriteType wt, FastBufferWriter.ForPrimitives _ = default) where T : unmanaged
|
||||||
|
{
|
||||||
|
switch (wt)
|
||||||
|
{
|
||||||
|
case WriteType.WriteDirect:
|
||||||
|
RunTypeTest(val);
|
||||||
|
break;
|
||||||
|
case WriteType.WriteSafe:
|
||||||
|
RunTypeTestSafe(val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void BaseTypeTest(Type testType, WriteType writeType)
|
public void BaseTypeTest(Type testType, WriteType writeType)
|
||||||
{
|
{
|
||||||
var random = new Random();
|
var random = new Random();
|
||||||
|
|
||||||
void RunTypeTestLocal<T>(T val, WriteType wt) where T : unmanaged
|
|
||||||
{
|
|
||||||
switch (wt)
|
|
||||||
{
|
|
||||||
case WriteType.WriteDirect:
|
|
||||||
RunTypeTest(val);
|
|
||||||
break;
|
|
||||||
case WriteType.WriteSafe:
|
|
||||||
RunTypeTestSafe(val);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testType == typeof(byte))
|
if (testType == typeof(byte))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal((byte)random.Next(), writeType);
|
RunTestWithWriteType((byte)random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(sbyte))
|
else if (testType == typeof(sbyte))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal((sbyte)random.Next(), writeType);
|
RunTestWithWriteType((sbyte)random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(short))
|
else if (testType == typeof(short))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal((short)random.Next(), writeType);
|
RunTestWithWriteType((short)random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(ushort))
|
else if (testType == typeof(ushort))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal((ushort)random.Next(), writeType);
|
RunTestWithWriteType((ushort)random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(int))
|
else if (testType == typeof(int))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal((int)random.Next(), writeType);
|
RunTestWithWriteType((int)random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(uint))
|
else if (testType == typeof(uint))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal((uint)random.Next(), writeType);
|
RunTestWithWriteType((uint)random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(long))
|
else if (testType == typeof(long))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(((long)random.Next() << 32) + random.Next(), writeType);
|
RunTestWithWriteType(((long)random.Next() << 32) + random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(ulong))
|
else if (testType == typeof(ulong))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(((ulong)random.Next() << 32) + (ulong)random.Next(), writeType);
|
RunTestWithWriteType(((ulong)random.Next() << 32) + (ulong)random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(bool))
|
else if (testType == typeof(bool))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(true, writeType);
|
RunTestWithWriteType(true, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(char))
|
else if (testType == typeof(char))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal('a', writeType);
|
RunTestWithWriteType('a', writeType);
|
||||||
RunTypeTestLocal('\u263a', writeType);
|
RunTestWithWriteType('\u263a', writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(float))
|
else if (testType == typeof(float))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal((float)random.NextDouble(), writeType);
|
RunTestWithWriteType((float)random.NextDouble(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(double))
|
else if (testType == typeof(double))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(random.NextDouble(), writeType);
|
RunTestWithWriteType(random.NextDouble(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(ByteEnum))
|
else if (testType == typeof(ByteEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(ByteEnum.C, writeType);
|
RunTestWithWriteType(ByteEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(SByteEnum))
|
else if (testType == typeof(SByteEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(SByteEnum.C, writeType);
|
RunTestWithWriteType(SByteEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(ShortEnum))
|
else if (testType == typeof(ShortEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(ShortEnum.C, writeType);
|
RunTestWithWriteType(ShortEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(UShortEnum))
|
else if (testType == typeof(UShortEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(UShortEnum.C, writeType);
|
RunTestWithWriteType(UShortEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(IntEnum))
|
else if (testType == typeof(IntEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(IntEnum.C, writeType);
|
RunTestWithWriteType(IntEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(UIntEnum))
|
else if (testType == typeof(UIntEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(UIntEnum.C, writeType);
|
RunTestWithWriteType(UIntEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(LongEnum))
|
else if (testType == typeof(LongEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(LongEnum.C, writeType);
|
RunTestWithWriteType(LongEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(ULongEnum))
|
else if (testType == typeof(ULongEnum))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(ULongEnum.C, writeType);
|
RunTestWithWriteType(ULongEnum.C, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Vector2))
|
else if (testType == typeof(Vector2))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Vector2((float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
RunTestWithWriteType(new Vector2((float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Vector3))
|
else if (testType == typeof(Vector3))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
RunTestWithWriteType(new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
||||||
|
}
|
||||||
|
else if (testType == typeof(Vector2Int))
|
||||||
|
{
|
||||||
|
RunTestWithWriteType(new Vector2Int((int)random.NextDouble(), (int)random.NextDouble()), writeType);
|
||||||
|
}
|
||||||
|
else if (testType == typeof(Vector3Int))
|
||||||
|
{
|
||||||
|
RunTestWithWriteType(new Vector3Int((int)random.NextDouble(), (int)random.NextDouble(), (int)random.NextDouble()), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Vector4))
|
else if (testType == typeof(Vector4))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Vector4((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
RunTestWithWriteType(new Vector4((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Quaternion))
|
else if (testType == typeof(Quaternion))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Quaternion((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
RunTestWithWriteType(new Quaternion((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Color))
|
else if (testType == typeof(Color))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
RunTestWithWriteType(new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Color32))
|
else if (testType == typeof(Color32))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Color32((byte)random.Next(), (byte)random.Next(), (byte)random.Next(), (byte)random.Next()), writeType);
|
RunTestWithWriteType(new Color32((byte)random.Next(), (byte)random.Next(), (byte)random.Next(), (byte)random.Next()), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Ray))
|
else if (testType == typeof(Ray))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Ray(
|
RunTestWithWriteType(new Ray(
|
||||||
new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()),
|
new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()),
|
||||||
new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble())), writeType);
|
new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble())), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(Ray2D))
|
else if (testType == typeof(Ray2D))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new Ray2D(
|
RunTestWithWriteType(new Ray2D(
|
||||||
new Vector2((float)random.NextDouble(), (float)random.NextDouble()),
|
new Vector2((float)random.NextDouble(), (float)random.NextDouble()),
|
||||||
new Vector2((float)random.NextDouble(), (float)random.NextDouble())), writeType);
|
new Vector2((float)random.NextDouble(), (float)random.NextDouble())), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(TestStruct))
|
else if (testType == typeof(TestStruct))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(GetTestStruct(), writeType);
|
RunTestWithWriteType(GetTestStruct(), writeType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -495,6 +503,22 @@ namespace Unity.Netcode.EditorTests
|
|||||||
new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()),
|
new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()),
|
||||||
}, writeType);
|
}, writeType);
|
||||||
}
|
}
|
||||||
|
else if (testType == typeof(Vector2Int))
|
||||||
|
{
|
||||||
|
RunTypeTestLocal(new[]{
|
||||||
|
new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()),
|
||||||
|
new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()),
|
||||||
|
new Vector2Int((int) random.NextDouble(), (int) random.NextDouble()),
|
||||||
|
}, writeType);
|
||||||
|
}
|
||||||
|
else if (testType == typeof(Vector3Int))
|
||||||
|
{
|
||||||
|
RunTypeTestLocal(new[]{
|
||||||
|
new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()),
|
||||||
|
new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()),
|
||||||
|
new Vector3Int((int) random.NextDouble(), (int) random.NextDouble(), (int) random.NextDouble()),
|
||||||
|
}, writeType);
|
||||||
|
}
|
||||||
else if (testType == typeof(Vector4))
|
else if (testType == typeof(Vector4))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new[]{
|
RunTypeTestLocal(new[]{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -51,6 +52,184 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RunWriteMethod<T>(string methodName, FastBufferWriter writer, in T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(T).MakeByRefType() });
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods())
|
||||||
|
{
|
||||||
|
if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition)
|
||||||
|
{
|
||||||
|
if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (candidateMethod.GetParameters()[0].ParameterType.IsArray)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method = candidateMethod.MakeGenericMethod(typeof(T));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
object[] args = new object[method.GetParameters().Length];
|
||||||
|
args[0] = value;
|
||||||
|
for (var i = 1; i < args.Length; ++i)
|
||||||
|
{
|
||||||
|
args[i] = method.GetParameters()[i].DefaultValue;
|
||||||
|
}
|
||||||
|
method.Invoke(writer, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunWriteMethod<T>(string methodName, FastBufferWriter writer, in T[] value) where T : unmanaged
|
||||||
|
{
|
||||||
|
MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(T[]) });
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods())
|
||||||
|
{
|
||||||
|
if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition)
|
||||||
|
{
|
||||||
|
if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!candidateMethod.GetParameters()[0].ParameterType.IsArray)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method = candidateMethod.MakeGenericMethod(typeof(T));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
object[] args = new object[method.GetParameters().Length];
|
||||||
|
args[0] = value;
|
||||||
|
for (var i = 1; i < args.Length; ++i)
|
||||||
|
{
|
||||||
|
args[i] = method.GetParameters()[i].DefaultValue;
|
||||||
|
}
|
||||||
|
method.Invoke(writer, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunReadMethod<T>(string methodName, FastBufferReader reader, out T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
MethodInfo method = typeof(FastBufferReader).GetMethod(methodName, new[] { typeof(T).MakeByRefType() });
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
foreach (var candidateMethod in typeof(FastBufferReader).GetMethods())
|
||||||
|
{
|
||||||
|
if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition)
|
||||||
|
{
|
||||||
|
if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (candidateMethod.GetParameters()[0].ParameterType.IsArray)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method = candidateMethod.MakeGenericMethod(typeof(T));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = new T();
|
||||||
|
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
object[] args = new object[method.GetParameters().Length];
|
||||||
|
args[0] = value;
|
||||||
|
for (var i = 1; i < args.Length; ++i)
|
||||||
|
{
|
||||||
|
args[i] = method.GetParameters()[i].DefaultValue;
|
||||||
|
}
|
||||||
|
method.Invoke(reader, args);
|
||||||
|
value = (T)args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunReadMethod<T>(string methodName, FastBufferReader reader, out T[] value) where T : unmanaged
|
||||||
|
{
|
||||||
|
MethodInfo method = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method = typeof(FastBufferReader).GetMethod(methodName, new[] { typeof(T[]).MakeByRefType() });
|
||||||
|
}
|
||||||
|
catch (AmbiguousMatchException)
|
||||||
|
{
|
||||||
|
// skip.
|
||||||
|
}
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
foreach (var candidateMethod in typeof(FastBufferReader).GetMethods())
|
||||||
|
{
|
||||||
|
if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition)
|
||||||
|
{
|
||||||
|
if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!candidateMethod.GetParameters()[0].ParameterType.HasElementType || !candidateMethod.GetParameters()[0].ParameterType.GetElementType().IsArray)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method = candidateMethod.MakeGenericMethod(typeof(T));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
value = new T[] { };
|
||||||
|
|
||||||
|
object[] args = new object[method.GetParameters().Length];
|
||||||
|
args[0] = value;
|
||||||
|
for (var i = 1; i < args.Length; ++i)
|
||||||
|
{
|
||||||
|
args[i] = method.GetParameters()[i].DefaultValue;
|
||||||
|
}
|
||||||
|
method.Invoke(reader, args);
|
||||||
|
value = (T[])args[0];
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Generic Checks
|
#region Generic Checks
|
||||||
@@ -66,14 +245,14 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
||||||
|
|
||||||
writer.WriteValue(valueToTest);
|
RunWriteMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest);
|
||||||
|
|
||||||
var reader = CommonChecks(writer, valueToTest, writeSize, failMessage);
|
var reader = CommonChecks(writer, valueToTest, writeSize, failMessage);
|
||||||
|
|
||||||
using (reader)
|
using (reader)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(reader.TryBeginRead(FastBufferWriter.GetWriteSize<T>()));
|
Assert.IsTrue(reader.TryBeginRead(FastBufferWriter.GetWriteSize<T>()));
|
||||||
reader.ReadValue(out T result);
|
RunReadMethod(nameof(FastBufferReader.ReadValue), reader, out T result);
|
||||||
Assert.AreEqual(valueToTest, result);
|
Assert.AreEqual(valueToTest, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,14 +268,14 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
||||||
|
|
||||||
writer.WriteValueSafe(valueToTest);
|
RunWriteMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest);
|
||||||
|
|
||||||
|
|
||||||
var reader = CommonChecks(writer, valueToTest, writeSize, failMessage);
|
var reader = CommonChecks(writer, valueToTest, writeSize, failMessage);
|
||||||
|
|
||||||
using (reader)
|
using (reader)
|
||||||
{
|
{
|
||||||
reader.ReadValueSafe(out T result);
|
RunReadMethod(nameof(FastBufferReader.ReadValueSafe), reader, out T result);
|
||||||
Assert.AreEqual(valueToTest, result);
|
Assert.AreEqual(valueToTest, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +300,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
||||||
Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
|
Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
|
||||||
|
|
||||||
writer.WriteValue(valueToTest);
|
RunWriteMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest);
|
||||||
|
|
||||||
WriteCheckBytes(writer, writeSize);
|
WriteCheckBytes(writer, writeSize);
|
||||||
|
|
||||||
@@ -131,7 +310,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
VerifyPositionAndLength(reader, writer.Length);
|
VerifyPositionAndLength(reader, writer.Length);
|
||||||
|
|
||||||
Assert.IsTrue(reader.TryBeginRead(writeSize));
|
Assert.IsTrue(reader.TryBeginRead(writeSize));
|
||||||
reader.ReadValue(out T[] result);
|
RunReadMethod(nameof(FastBufferReader.ReadValue), reader, out T[] result);
|
||||||
VerifyArrayEquality(valueToTest, result, 0);
|
VerifyArrayEquality(valueToTest, result, 0);
|
||||||
|
|
||||||
VerifyCheckBytes(reader, writeSize);
|
VerifyCheckBytes(reader, writeSize);
|
||||||
@@ -147,7 +326,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
||||||
|
|
||||||
writer.WriteValueSafe(valueToTest);
|
RunWriteMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest);
|
||||||
|
|
||||||
WriteCheckBytes(writer, writeSize);
|
WriteCheckBytes(writer, writeSize);
|
||||||
|
|
||||||
@@ -156,7 +335,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
VerifyPositionAndLength(reader, writer.Length);
|
VerifyPositionAndLength(reader, writer.Length);
|
||||||
|
|
||||||
reader.ReadValueSafe(out T[] result);
|
RunReadMethod(nameof(FastBufferReader.ReadValueSafe), reader, out T[] result);
|
||||||
VerifyArrayEquality(valueToTest, result, 0);
|
VerifyArrayEquality(valueToTest, result, 0);
|
||||||
|
|
||||||
VerifyCheckBytes(reader, writeSize);
|
VerifyCheckBytes(reader, writeSize);
|
||||||
@@ -172,8 +351,9 @@ namespace Unity.Netcode.EditorTests
|
|||||||
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
||||||
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
||||||
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
||||||
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
|
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3),
|
||||||
typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color),
|
||||||
|
typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
||||||
Type testType,
|
Type testType,
|
||||||
[Values] WriteType writeType)
|
[Values] WriteType writeType)
|
||||||
{
|
{
|
||||||
@@ -185,14 +365,156 @@ namespace Unity.Netcode.EditorTests
|
|||||||
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
||||||
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
||||||
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
||||||
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
|
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3),
|
||||||
typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color),
|
||||||
|
typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
||||||
Type testType,
|
Type testType,
|
||||||
[Values] WriteType writeType)
|
[Values] WriteType writeType)
|
||||||
{
|
{
|
||||||
BaseArrayTypeTest(testType, writeType);
|
BaseArrayTypeTest(testType, writeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public unsafe void RunFixedStringTest<T>(T fixedStringValue, int numBytesWritten, WriteType writeType) where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
|
{
|
||||||
|
fixedStringValue.Length = numBytesWritten;
|
||||||
|
|
||||||
|
var serializedValueSize = FastBufferWriter.GetWriteSize(fixedStringValue);
|
||||||
|
|
||||||
|
Assert.AreEqual(serializedValueSize, fixedStringValue.Length + sizeof(int));
|
||||||
|
|
||||||
|
var writer = new FastBufferWriter(serializedValueSize + 3, Allocator.Temp);
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
var offset = 0;
|
||||||
|
switch (writeType)
|
||||||
|
{
|
||||||
|
case WriteType.WriteDirect:
|
||||||
|
Assert.IsTrue(writer.TryBeginWrite(serializedValueSize + 2), "Writer denied write permission");
|
||||||
|
writer.WriteValue(fixedStringValue);
|
||||||
|
break;
|
||||||
|
case WriteType.WriteSafe:
|
||||||
|
writer.WriteValueSafe(fixedStringValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteCheckBytes(writer, serializedValueSize + offset);
|
||||||
|
|
||||||
|
var reader = new FastBufferReader(writer, Allocator.Temp);
|
||||||
|
using (reader)
|
||||||
|
{
|
||||||
|
VerifyPositionAndLength(reader, writer.Length);
|
||||||
|
|
||||||
|
var result = new T();
|
||||||
|
reader.ReadValueSafe(out result);
|
||||||
|
Assert.AreEqual(fixedStringValue, result);
|
||||||
|
|
||||||
|
VerifyCheckBytes(reader, serializedValueSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
public void WhenReadingFixedString32Bytes_ValueIsReadCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 29; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString32Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
public void WhenReadingFixedString64Bytes_ValueIsReadCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 61; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString64Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
[TestCase(125, WriteType.WriteSafe)]
|
||||||
|
public void WhenReadingFixedString128Bytes_ValueIsReadCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 125; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString128Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
[TestCase(125, WriteType.WriteSafe)]
|
||||||
|
[TestCase(509, WriteType.WriteSafe)]
|
||||||
|
public void WhenReadingFixedString512Bytes_ValueIsReadCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 509; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString512Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
[TestCase(125, WriteType.WriteSafe)]
|
||||||
|
[TestCase(509, WriteType.WriteSafe)]
|
||||||
|
[TestCase(4093, WriteType.WriteSafe)]
|
||||||
|
public void WhenReadingFixedString4096Bytes_ValueIsReadCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 4093; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString4096Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(false, WriteType.WriteDirect)]
|
[TestCase(false, WriteType.WriteDirect)]
|
||||||
[TestCase(false, WriteType.WriteSafe)]
|
[TestCase(false, WriteType.WriteSafe)]
|
||||||
[TestCase(true, WriteType.WriteDirect)]
|
[TestCase(true, WriteType.WriteDirect)]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -10,6 +11,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
|
|
||||||
#region Common Checks
|
#region Common Checks
|
||||||
|
|
||||||
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
|
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
|
||||||
{
|
{
|
||||||
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
|
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
|
||||||
@@ -63,9 +65,94 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
VerifyTypedEquality(valueToTest, writer.GetUnsafePtr());
|
VerifyTypedEquality(valueToTest, writer.GetUnsafePtr());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Generic Checks
|
#region Generic Checks
|
||||||
|
|
||||||
|
private void RunMethod<T>(string methodName, FastBufferWriter writer, in T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(T).MakeByRefType() });
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods())
|
||||||
|
{
|
||||||
|
if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition)
|
||||||
|
{
|
||||||
|
if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (candidateMethod.GetParameters()[0].ParameterType.IsArray)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method = candidateMethod.MakeGenericMethod(typeof(T));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
object[] args = new object[method.GetParameters().Length];
|
||||||
|
args[0] = value;
|
||||||
|
for (var i = 1; i < args.Length; ++i)
|
||||||
|
{
|
||||||
|
args[i] = method.GetParameters()[i].DefaultValue;
|
||||||
|
}
|
||||||
|
method.Invoke(writer, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunMethod<T>(string methodName, FastBufferWriter writer, in T[] value) where T : unmanaged
|
||||||
|
{
|
||||||
|
MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(T[]) });
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
foreach (var candidateMethod in typeof(FastBufferWriter).GetMethods())
|
||||||
|
{
|
||||||
|
if (candidateMethod.Name == methodName && candidateMethod.IsGenericMethodDefinition)
|
||||||
|
{
|
||||||
|
if (candidateMethod.GetParameters().Length == 0 || (candidateMethod.GetParameters().Length > 1 && !candidateMethod.GetParameters()[1].HasDefaultValue))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!candidateMethod.GetParameters()[0].ParameterType.IsArray)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method = candidateMethod.MakeGenericMethod(typeof(T));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(method);
|
||||||
|
|
||||||
|
object[] args = new object[method.GetParameters().Length];
|
||||||
|
args[0] = value;
|
||||||
|
for (var i = 1; i < args.Length; ++i)
|
||||||
|
{
|
||||||
|
args[i] = method.GetParameters()[i].DefaultValue;
|
||||||
|
}
|
||||||
|
method.Invoke(writer, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override unsafe void RunTypeTest<T>(T valueToTest)
|
protected override unsafe void RunTypeTest<T>(T valueToTest)
|
||||||
{
|
{
|
||||||
var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
|
var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
|
||||||
@@ -82,7 +169,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
||||||
|
|
||||||
writer.WriteValue(valueToTest);
|
RunMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest);
|
||||||
|
|
||||||
CommonChecks(writer, valueToTest, writeSize, failMessage);
|
CommonChecks(writer, valueToTest, writeSize, failMessage);
|
||||||
}
|
}
|
||||||
@@ -98,7 +185,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
|
||||||
|
|
||||||
writer.WriteValueSafe(valueToTest);
|
RunMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest);
|
||||||
|
|
||||||
CommonChecks(writer, valueToTest, writeSize, failMessage);
|
CommonChecks(writer, valueToTest, writeSize, failMessage);
|
||||||
}
|
}
|
||||||
@@ -129,7 +216,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
||||||
Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
|
Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
|
||||||
|
|
||||||
writer.WriteValue(valueToTest);
|
RunMethod(nameof(FastBufferWriter.WriteValue), writer, valueToTest);
|
||||||
VerifyPositionAndLength(writer, writeSize);
|
VerifyPositionAndLength(writer, writeSize);
|
||||||
|
|
||||||
WriteCheckBytes(writer, writeSize);
|
WriteCheckBytes(writer, writeSize);
|
||||||
@@ -150,7 +237,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
|
||||||
|
|
||||||
writer.WriteValueSafe(valueToTest);
|
RunMethod(nameof(FastBufferWriter.WriteValueSafe), writer, valueToTest);
|
||||||
VerifyPositionAndLength(writer, writeSize);
|
VerifyPositionAndLength(writer, writeSize);
|
||||||
|
|
||||||
WriteCheckBytes(writer, writeSize);
|
WriteCheckBytes(writer, writeSize);
|
||||||
@@ -170,8 +257,9 @@ namespace Unity.Netcode.EditorTests
|
|||||||
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
||||||
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
||||||
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
||||||
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
|
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3),
|
||||||
typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color),
|
||||||
|
typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
||||||
Type testType,
|
Type testType,
|
||||||
[Values] WriteType writeType)
|
[Values] WriteType writeType)
|
||||||
{
|
{
|
||||||
@@ -183,8 +271,9 @@ namespace Unity.Netcode.EditorTests
|
|||||||
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
||||||
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
|
||||||
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
|
||||||
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
|
typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3),
|
||||||
typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
typeof(Vector2Int), typeof(Vector3Int), typeof(Vector4), typeof(Quaternion), typeof(Color),
|
||||||
|
typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
|
||||||
Type testType,
|
Type testType,
|
||||||
[Values] WriteType writeType)
|
[Values] WriteType writeType)
|
||||||
{
|
{
|
||||||
@@ -249,6 +338,147 @@ namespace Unity.Netcode.EditorTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe void RunFixedStringTest<T>(T fixedStringValue, int numBytesWritten, WriteType writeType) where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
|
{
|
||||||
|
fixedStringValue.Length = numBytesWritten;
|
||||||
|
|
||||||
|
var serializedValueSize = FastBufferWriter.GetWriteSize(fixedStringValue);
|
||||||
|
|
||||||
|
Assert.AreEqual(fixedStringValue.Length + sizeof(int), serializedValueSize);
|
||||||
|
|
||||||
|
var writer = new FastBufferWriter(serializedValueSize + 3, Allocator.Temp);
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
var offset = 0;
|
||||||
|
switch (writeType)
|
||||||
|
{
|
||||||
|
case WriteType.WriteDirect:
|
||||||
|
Assert.IsTrue(writer.TryBeginWrite(serializedValueSize + 2), "Writer denied write permission");
|
||||||
|
writer.WriteValue(fixedStringValue);
|
||||||
|
break;
|
||||||
|
case WriteType.WriteSafe:
|
||||||
|
writer.WriteValueSafe(fixedStringValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VerifyPositionAndLength(writer, serializedValueSize + offset);
|
||||||
|
WriteCheckBytes(writer, serializedValueSize + offset);
|
||||||
|
|
||||||
|
int* sizeValue = (int*)(writer.GetUnsafePtr() + offset);
|
||||||
|
Assert.AreEqual(fixedStringValue.Length, *sizeValue);
|
||||||
|
|
||||||
|
byte* underlyingByteArray = writer.GetUnsafePtr() + sizeof(int) + offset;
|
||||||
|
for (var i = 0; i < fixedStringValue.Length; ++i)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(fixedStringValue[i], underlyingByteArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var underlyingArray = writer.ToArray();
|
||||||
|
VerifyCheckBytes(underlyingArray, serializedValueSize + offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
public void WhenWritingFixedString32Bytes_ValueIsWrittenCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 29; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString32Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
public void WhenWritingFixedString64Bytes_ValueIsWrittenCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 61; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString64Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
[TestCase(125, WriteType.WriteSafe)]
|
||||||
|
public void WhenWritingFixedString128Bytes_ValueIsWrittenCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 125; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString128Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
[TestCase(125, WriteType.WriteSafe)]
|
||||||
|
[TestCase(509, WriteType.WriteSafe)]
|
||||||
|
public void WhenWritingFixedString512Bytes_ValueIsWrittenCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 509; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString512Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(3, WriteType.WriteDirect)]
|
||||||
|
[TestCase(5, WriteType.WriteSafe)]
|
||||||
|
[TestCase(16, WriteType.WriteDirect)]
|
||||||
|
[TestCase(29, WriteType.WriteSafe)]
|
||||||
|
[TestCase(61, WriteType.WriteSafe)]
|
||||||
|
[TestCase(125, WriteType.WriteSafe)]
|
||||||
|
[TestCase(509, WriteType.WriteSafe)]
|
||||||
|
[TestCase(4093, WriteType.WriteSafe)]
|
||||||
|
public void WhenWritingFixedString4096Bytes_ValueIsWrittenCorrectly(int numBytesWritten, WriteType writeType)
|
||||||
|
{
|
||||||
|
// Repeats 01234567890123456789...
|
||||||
|
string valueToTest = "";
|
||||||
|
for (var i = 0; i < 4093; ++i)
|
||||||
|
{
|
||||||
|
valueToTest += (i % 10).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedStringValue = new FixedString4096Bytes(valueToTest);
|
||||||
|
|
||||||
|
RunFixedStringTest(fixedStringValue, numBytesWritten, writeType);
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(1, 0)]
|
[TestCase(1, 0)]
|
||||||
[TestCase(2, 0)]
|
[TestCase(2, 0)]
|
||||||
[TestCase(3, 0)]
|
[TestCase(3, 0)]
|
||||||
|
|||||||
49
Tests/Editor/Transports/UNetTransportTests.cs
Normal file
49
Tests/Editor/Transports/UNetTransportTests.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#if UNITY_UNET_PRESENT
|
||||||
|
#pragma warning disable 618 // disable is obsolete
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.Transports.UNET;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
|
namespace Unity.Netcode.EditorTests
|
||||||
|
{
|
||||||
|
public class UNetTransportTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void StartServerReturnsFalseOnFailure()
|
||||||
|
{
|
||||||
|
UNetTransport unet1 = null;
|
||||||
|
UNetTransport unet2 = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
// We're expecting an error from UNET, but don't care to validate the specific message
|
||||||
|
LogAssert.ignoreFailingMessages = true;
|
||||||
|
|
||||||
|
var go = new GameObject();
|
||||||
|
unet1 = go.AddComponent<UNetTransport>();
|
||||||
|
unet1.ServerListenPort = 1;
|
||||||
|
unet1.Initialize();
|
||||||
|
unet1.StartServer();
|
||||||
|
unet2 = go.AddComponent<UNetTransport>();
|
||||||
|
unet2.ServerListenPort = 1;
|
||||||
|
unet2.Initialize();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = unet2.StartServer();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(result, "UNET fails to initialize against port already in use");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
unet1?.Shutdown();
|
||||||
|
unet2?.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 618
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 54c9647dc784a46bca664910f182491e
|
guid: 6e328ef8f7c9b46538253a1b39dc8a97
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user