3 Commits

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

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

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

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

## [1.0.0-pre.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)

### 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).

### Fixed

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

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

## [1.0.0-pre.7] - 2022-04-01

### Added

- Added editor only check prior to entering into play mode if the currently open and active scene is in the build list and if not displays a dialog box asking the user if they would like to automatically add it prior to entering into play mode. (#1828)
- Added `UnityTransport` implementation and `com.unity.transport` package dependency (#1823)
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)
- `UnityTransport` settings can now be set programmatically. (#1845)
- `FastBufferWriter` and Reader IsInitialized property. (#1859)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849)

### Removed

- Removed `SnapshotSystem` (#1852)
- Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812)
- Removed `com.unity.collections` dependency from the package (#1849)

### Fixed
- 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 NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
- Fixed issue where entries were not being removed from the NetworkSpawnManager.OwnershipToObjectsTable. (#1838)
- Fixed ClientRpcs would always send to all connected clients by default as opposed to only sending to the NetworkObject's Observers list by default. (#1836)
- Fixed clarity for NetworkSceneManager client side notification when it receives a scene hash value that does not exist in its local hash table. (#1828)
- Fixed client throws a key not found exception when it times out using UNet or UTP. (#1821)
- Fixed network variable updates are no longer limited to 32,768 bytes when NetworkConfig.EnsureNetworkVariableLengthSafety is enabled. The limits are now determined by what the transport can send in a message. (#1811)
- Fixed in-scene NetworkObjects get destroyed if a client fails to connect and shuts down the NetworkManager. (#1809)
- Fixed user never being notified in the editor that a NetworkBehaviour requires a NetworkObject to function properly. (#1808)
- Fixed PlayerObjects and dynamically spawned NetworkObjects not being added to the NetworkClient's OwnedObjects (#1801)
- Fixed issue where NetworkManager would continue starting even if the NetworkTransport selected failed. (#1780)
- Fixed issue when spawning new player if an already existing player exists it does not remove IsPlayer from the previous player (#1779)
- Fixed lack of notification that NetworkManager and NetworkObject cannot be added to the same GameObject with in-editor notifications (#1777)
- Fixed parenting warning printing for false positives (#1855)
2022-04-01 00:00:00 +00:00
200 changed files with 10255 additions and 5177 deletions

View File

@@ -6,14 +6,81 @@ 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.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)
### 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 `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 editor only check prior to entering into play mode if the currently open and active scene is in the build list and if not displays a dialog box asking the user if they would like to automatically add it prior to entering into play mode. (#1828)
- Added `UnityTransport` implementation and `com.unity.transport` package dependency (#1823)
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)
- `UnityTransport` settings can now be set programmatically. (#1845)
- `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 `NetcodeIntegrationTestHelpers` to use `UnityTransport` (#1870)
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849)
### Removed
- Removed `SnapshotSystem` (#1852)
- Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812)
- Removed `com.unity.collections` dependency from the package (#1849)
### Fixed
- 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 NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
- Fixed issue where entries were not being removed from the NetworkSpawnManager.OwnershipToObjectsTable. (#1838)
- Fixed ClientRpcs would always send to all connected clients by default as opposed to only sending to the NetworkObject's Observers list by default. (#1836)
- Fixed clarity for NetworkSceneManager client side notification when it receives a scene hash value that does not exist in its local hash table. (#1828)
- Fixed client throws a key not found exception when it times out using UNet or UTP. (#1821)
- Fixed network variable updates are no longer limited to 32,768 bytes when NetworkConfig.EnsureNetworkVariableLengthSafety is enabled. The limits are now determined by what the transport can send in a message. (#1811)
- Fixed in-scene NetworkObjects get destroyed if a client fails to connect and shuts down the NetworkManager. (#1809)
- Fixed user never being notified in the editor that a NetworkBehaviour requires a NetworkObject to function properly. (#1808)
- Fixed PlayerObjects and dynamically spawned NetworkObjects not being added to the NetworkClient's OwnedObjects (#1801)
- Fixed issue where NetworkManager would continue starting even if the NetworkTransport selected failed. (#1780)
- Fixed issue when spawning new player if an already existing player exists it does not remove IsPlayer from the previous player (#1779)
- Fixed lack of notification that NetworkManager and NetworkObject cannot be added to the same GameObject with in-editor notifications (#1777)
- Fixed parenting warning printing for false positives (#1855)
## [1.0.0-pre.6] - 2022-03-02 ## [1.0.0-pre.6] - 2022-03-02
### Added ### Added
- 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)
@@ -33,6 +100,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731) - Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731)
- Improved performance in NetworkAnimator (#1735) - Improved performance in NetworkAnimator (#1735)
- Removed the "always sync" network animator (aka "autosend") parameters (#1746) - Removed the "always sync" network animator (aka "autosend") parameters (#1746)
- Fixed in-scene placed NetworkObjects not respawning after shutting down the NetworkManager and then starting it back up again (#1769)
## [1.0.0-pre.5] - 2022-01-26 ## [1.0.0-pre.5] - 2022-01-26

View File

@@ -4,13 +4,11 @@ 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
{ {
private struct BufferedItem private struct BufferedItem
{ {
@@ -24,6 +22,10 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Theres two factors affecting interpolation: buffering (set in NetworkManagers NetworkTimeSystem) and interpolation time, which is the amount of time itll 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 +71,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 +81,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 +97,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)
{ {
@@ -186,7 +193,14 @@ namespace Unity.Netcode
if (t < 0.0f) if (t < 0.0f)
{ {
throw new OverflowException($"t = {t} but must be >= 0. range {range}, RenderTime {renderTime}, Start time {m_StartTimeConsumed}, end time {m_EndTimeConsumed}"); // There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed
// This clamps t to a minimum of 0 and fixes issues with longer frames and pauses
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}");
}
t = 0.0f;
} }
if (t > 3.0f) // max extrapolation if (t > 3.0f) // max extrapolation
@@ -198,14 +212,16 @@ namespace Unity.Netcode
} }
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++;
@@ -218,6 +234,8 @@ namespace Unity.Netcode
{ {
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
ResetTo(newMeasurement, sentTime); ResetTo(newMeasurement, sentTime);
// Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues
m_Buffer.Add(m_LastBufferedItemReceived);
} }
return; return;
@@ -230,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)
{ {
@@ -253,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)
{ {

View File

@@ -1,3 +1,4 @@
#if COM_UNITY_MODULES_ANIMATION
using Unity.Collections; using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using UnityEngine; using UnityEngine;
@@ -5,7 +6,7 @@ using UnityEngine;
namespace Unity.Netcode.Components namespace Unity.Netcode.Components
{ {
/// <summary> /// <summary>
/// A prototype component for syncing animations /// NetworkAnimator enables remote synchronization of <see cref="UnityEngine.Animator"/> state for on network objects.
/// </summary> /// </summary>
[AddComponentMenu("Netcode/" + nameof(NetworkAnimator))] [AddComponentMenu("Netcode/" + nameof(NetworkAnimator))]
[RequireComponent(typeof(Animator))] [RequireComponent(typeof(Animator))]
@@ -14,11 +15,11 @@ 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; internal byte[] Parameters;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{ {
@@ -32,8 +33,8 @@ namespace Unity.Netcode.Components
internal struct AnimationTriggerMessage : INetworkSerializable internal struct AnimationTriggerMessage : INetworkSerializable
{ {
public int Hash; internal int Hash;
public bool Reset; internal bool Reset;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{ {
@@ -56,7 +57,7 @@ namespace Unity.Netcode.Components
private bool m_SendMessagesAllowed = false; private bool m_SendMessagesAllowed = false;
// 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;
@@ -64,21 +65,21 @@ namespace Unity.Netcode.Components
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;
static AnimationParamEnumWrapper() static AnimationParamEnumWrapper()
{ {
@@ -380,9 +381,7 @@ namespace Unity.Netcode.Components
SetTrigger(Animator.StringToHash(triggerName)); SetTrigger(Animator.StringToHash(triggerName));
} }
/// <summary> /// <inheritdoc cref="SetTrigger(string)" />
/// Sets the trigger for the associated animation. See note for SetTrigger(string)
/// </summary>
/// <param name="hash">The hash for the trigger to activate</param> /// <param name="hash">The hash for the trigger to activate</param>
/// <param name="reset">If true, resets the trigger</param> /// <param name="reset">If true, resets the trigger</param>
public void SetTrigger(int hash, bool reset = false) public void SetTrigger(int hash, bool reset = false)
@@ -413,7 +412,7 @@ namespace Unity.Netcode.Components
} }
/// <summary> /// <summary>
/// Resets the trigger for the associated animation. See note for SetTrigger(string) /// Resets the trigger for the associated animation. See <see cref="SetTrigger(string)">SetTrigger</see> for more on how triggers are special
/// </summary> /// </summary>
/// <param name="triggerName">The string name of the trigger to reset</param> /// <param name="triggerName">The string name of the trigger to reset</param>
public void ResetTrigger(string triggerName) public void ResetTrigger(string triggerName)
@@ -421,13 +420,12 @@ namespace Unity.Netcode.Components
ResetTrigger(Animator.StringToHash(triggerName)); ResetTrigger(Animator.StringToHash(triggerName));
} }
/// <summary> /// <inheritdoc cref="ResetTrigger(string)" path="summary" />
/// Resets the trigger for the associated animation. See note for SetTrigger(string) /// <param name="hash">The hash for the trigger to activate</param>
/// </summary>
/// <param name="hash">The hash for the trigger to reset</param>
public void ResetTrigger(int hash) public void ResetTrigger(int hash)
{ {
SetTrigger(hash, true); SetTrigger(hash, true);
} }
} }
} }
#endif // COM_UNITY_MODULES_ANIMATION

View File

@@ -1,80 +1,102 @@
#if COM_UNITY_MODULES_PHYSICS
using UnityEngine; using UnityEngine;
namespace Unity.Netcode.Components 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;
} }
} }
} }
#endif // COM_UNITY_MODULES_PHYSICS

View File

@@ -1,3 +1,4 @@
#if COM_UNITY_MODULES_PHYSICS2D
using UnityEngine; using UnityEngine;
namespace Unity.Netcode.Components namespace Unity.Netcode.Components
@@ -78,3 +79,4 @@ namespace Unity.Netcode.Components
} }
} }
} }
#endif // COM_UNITY_MODULES_PHYSICS2D

View File

@@ -15,9 +15,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 +39,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 +50,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 +60,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 +70,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 +81,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 +91,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 +101,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 +112,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 +122,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 +132,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 +142,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 +152,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 +168,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 +179,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 +250,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>
@@ -272,7 +276,7 @@ namespace Unity.Netcode.Components
/// If using different values, please use RPCs to write to the server. Netcode doesn't support client side network variable writing /// If using different values, please use RPCs to write to the server. Netcode doesn't support client side network variable writing
/// </summary> /// </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 // 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 bool CanCommitToTransform; public bool CanCommitToTransform { get; protected set; }
protected bool m_CachedIsServer; protected bool m_CachedIsServer;
protected NetworkManager m_CachedNetworkManager; protected NetworkManager m_CachedNetworkManager;
@@ -280,8 +284,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 +392,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 +462,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 +470,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 +478,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 +521,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 +597,6 @@ namespace Unity.Netcode.Components
{ {
transformToUpdate.position = interpolatedPosition; transformToUpdate.position = interpolatedPosition;
} }
m_PrevNetworkState.Position = interpolatedPosition;
} }
// RotAngles Apply // RotAngles Apply
@@ -602,15 +610,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;
} }
} }
@@ -691,7 +696,6 @@ namespace Unity.Netcode.Components
{ {
if (!NetworkObject.IsSpawned) if (!NetworkObject.IsSpawned)
{ {
// todo MTT-849 should never happen but yet it does! maybe revisit/dig after NetVar updates and snapshot system lands?
return; return;
} }
@@ -785,14 +789,12 @@ namespace Unity.Netcode.Components
{ {
m_ReplicatedNetworkState.SetDirty(true); m_ReplicatedNetworkState.SetDirty(true);
} }
else else if (m_Transform != null)
{ {
ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform); ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform);
} }
} }
#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
@@ -852,7 +854,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.
@@ -880,8 +881,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
@@ -905,36 +904,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;
} }
@@ -961,5 +934,22 @@ 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 and return false to follow the owner authoritative
/// Otherwise, it defaults to server authoritative
/// </summary>
protected virtual bool OnIsServerAuthoritatitive()
{
return true;
}
/// <summary>
/// Used by <see cref="NetworkRigidbody"/> to determines if this is server or owner authoritative.
/// </summary>
internal bool IsServerAuthoritative()
{
return OnIsServerAuthoritatitive();
}
} }
} }

View File

@@ -5,5 +5,22 @@
"Unity.Netcode.Runtime", "Unity.Netcode.Runtime",
"Unity.Collections" "Unity.Collections"
], ],
"allowUnsafeCode": true "allowUnsafeCode": true,
"versionDefines": [
{
"name": "com.unity.modules.animation",
"expression": "",
"define": "COM_UNITY_MODULES_ANIMATION"
},
{
"name": "com.unity.modules.physics",
"expression": "",
"define": "COM_UNITY_MODULES_PHYSICS"
},
{
"name": "com.unity.modules.physics2d",
"expression": "",
"define": "COM_UNITY_MODULES_PHYSICS2D"
}
]
} }

View File

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

View File

@@ -27,6 +27,7 @@ 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 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 +78,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)

View File

@@ -24,6 +24,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 +55,6 @@ namespace Unity.Netcode.Editor.CodeGen
return null; return null;
} }
m_Diagnostics.Clear(); m_Diagnostics.Clear();
// read // read
@@ -50,16 +73,128 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
if (ImportReferences(mainModule)) if (ImportReferences(mainModule))
{ {
var types = mainModule.GetTypes() // Initialize all the delegates for various NetworkVariable types to ensure they can be serailized
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && t.Resolve().IsValueType)
// Find all types we know we're going to want to serialize.
// The list of these types includes:
// - Non-generic INetworkSerializable types
// - Non-Generic INetworkSerializeByMemcpy types
// - Enums that are not declared within generic types
// We can't process generic types because, to initialize a generic, we need a value
// for `T` to initialize it with.
var networkSerializableTypes = mainModule.GetTypes()
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
.ToList(); .ToList();
// process `INetworkMessage` types var structTypes = mainModule.GetTypes()
if (types.Count == 0) .Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
.ToList();
var enumTypes = mainModule.GetTypes()
.Where(t => t.Resolve().IsEnum && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
.ToList();
// Now, to support generics, we have to do an extra pass.
// We look for any type that's a NetworkBehaviour type
// Then we look through all the fields in that type, finding any field whose type is
// descended from `NetworkVariableSerialization`. Then we check `NetworkVariableSerialization`'s
// `T` value, and if it's a generic, then we know it was missed in the above sweep and needs
// to be initialized. Now we have a full generic instance rather than a generic definition,
// so we can validly generate an initializer for that particular instance of the generic type.
var networkSerializableTypesSet = new HashSet<TypeReference>(networkSerializableTypes);
var structTypesSet = new HashSet<TypeReference>(structTypes);
var enumTypesSet = new HashSet<TypeReference>(enumTypes);
var typeStack = new List<TypeReference>();
foreach (var type in mainModule.GetTypes())
{
// Check if it's a NetworkBehaviour
if (type.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName))
{
// Iterate fields looking for NetworkVariableSerialization fields
foreach (var field in type.Fields)
{
// Get the field type and its base type
var fieldType = field.FieldType;
var baseType = fieldType.Resolve().BaseType;
if (baseType == null)
{
continue;
}
// This type stack is used for resolving NetworkVariableSerialization's T value
// When looking at base types, we get the type definition rather than the
// type reference... which means that we get the generic definition with an
// undefined T rather than the instance with the type filled in.
// We then have to walk backward back down the type stack to resolve what T
// is.
typeStack.Clear();
typeStack.Add(fieldType);
// Iterate through the base types until we get to Object.
// Object is the base for everything so we'll stop when we hit that.
while (baseType.Name != mainModule.TypeSystem.Object.Name)
{
// If we've found a NetworkVariableSerialization type...
if (baseType.IsGenericInstance && baseType.Resolve() == m_NetworkVariableSerializationType)
{
// Then we need to figure out what T is
var genericType = (GenericInstanceType)baseType;
var underlyingType = genericType.GenericArguments[0];
if (underlyingType.Resolve() == null)
{
underlyingType = ResolveGenericType(underlyingType, typeStack);
}
// If T is generic...
if (underlyingType.IsGenericInstance)
{
// Then we pick the correct set to add it to and set it up
// for initialization.
if (underlyingType.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
{
networkSerializableTypesSet.Add(underlyingType);
}
if (underlyingType.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
{
structTypesSet.Add(underlyingType);
}
if (underlyingType.Resolve().IsEnum)
{
enumTypesSet.Add(underlyingType);
}
}
break;
}
typeStack.Add(baseType);
baseType = baseType.Resolve().BaseType;
}
}
}
// We'll also 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.
else 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.");
}
}
}
if (networkSerializableTypes.Count + structTypes.Count + enumTypes.Count == 0)
{ {
return null; return null;
} }
CreateModuleInitializer(assemblyDefinition, types); // Finally we add to the module initializer some code to initialize the delegates in
// NetworkVariableSerialization<T> for all necessary values of T, by calling initialization
// methods in NetworkVariableHelpers.
CreateModuleInitializer(assemblyDefinition, networkSerializableTypesSet.ToList(), structTypesSet.ToList(), enumTypesSet.ToList());
} }
else else
{ {
@@ -94,9 +229,15 @@ 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 MethodReference m_InitializeDelegatesNetworkSerializable_MethodRef;
private MethodReference m_InitializeDelegatesStruct_MethodRef;
private MethodReference m_InitializeDelegatesEnum_MethodRef;
private const string k_InitializeMethodName = nameof(NetworkVariableHelper.InitializeDelegates); private TypeDefinition m_NetworkVariableSerializationType;
private const string k_InitializeNetworkSerializableMethodName = nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable);
private const string k_InitializeStructMethodName = nameof(NetworkVariableHelper.InitializeDelegatesStruct);
private const string k_InitializeEnumMethodName = nameof(NetworkVariableHelper.InitializeDelegatesEnum);
private bool ImportReferences(ModuleDefinition moduleDefinition) private bool ImportReferences(ModuleDefinition moduleDefinition)
{ {
@@ -106,11 +247,18 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
switch (methodInfo.Name) switch (methodInfo.Name)
{ {
case k_InitializeMethodName: case k_InitializeNetworkSerializableMethodName:
m_InitializeDelegates_MethodRef = moduleDefinition.ImportReference(methodInfo); m_InitializeDelegatesNetworkSerializable_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_InitializeStructMethodName:
m_InitializeDelegatesStruct_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_InitializeEnumMethodName:
m_InitializeDelegatesEnum_MethodRef = moduleDefinition.ImportReference(methodInfo);
break; break;
} }
} }
m_NetworkVariableSerializationType = moduleDefinition.ImportReference(typeof(NetworkVariableSerialization<>)).Resolve();
return true; return true;
} }
@@ -139,7 +287,7 @@ namespace Unity.Netcode.Editor.CodeGen
// C# (that attribute doesn't exist in Unity, but the static module constructor still works) // 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://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 // 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) private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeReference> networkSerializableTypes, List<TypeReference> structTypes, List<TypeReference> enumTypes)
{ {
foreach (var typeDefinition in assembly.MainModule.Types) foreach (var typeDefinition in assembly.MainModule.Types)
{ {
@@ -151,9 +299,23 @@ namespace Unity.Netcode.Editor.CodeGen
var instructions = new List<Instruction>(); var instructions = new List<Instruction>();
foreach (var type in structTypes)
{
var method = new GenericInstanceMethod(m_InitializeDelegatesStruct_MethodRef);
method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method));
}
foreach (var type in networkSerializableTypes) foreach (var type in networkSerializableTypes)
{ {
var method = new GenericInstanceMethod(m_InitializeDelegates_MethodRef); var method = new GenericInstanceMethod(m_InitializeDelegatesNetworkSerializable_MethodRef);
method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method));
}
foreach (var type in enumTypes)
{
var method = new GenericInstanceMethod(m_InitializeDelegatesEnum_MethodRef);
method.GenericArguments.Add(type); method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method)); instructions.Add(processor.Create(OpCodes.Call, method));
} }

View File

@@ -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,15 @@ 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();
#else
var resolvedConstraint = constraint.ConstraintType.Resolve();
#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 ((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 +626,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 +681,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,29 +721,58 @@ 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();
#else
var resolvedConstraint = constraint.ConstraintType.Resolve();
#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 ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraintName)) ||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraintName)) ||
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
{ {
var instanceMethod = new GenericInstanceMethod(method); meetsConstraints = false;
instanceMethod.GenericArguments.Add(checkType); break;
return instanceMethod;
} }
} }
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 +802,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 +1048,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 +1111,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, $"Don't know how to serialize {paramType.Name} - implement {nameof(INetworkSerializable)}, tag memcpyable struct with {nameof(INetworkSerializeByMemcpy)}, or add an extension method for {nameof(FastBufferWriter)}.{k_WriteValueMethodName} to define serialization.");
continue; continue;
} }
@@ -1298,6 +1381,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 +1425,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, $"Don't know how to serialize {paramType.Name} - implement {nameof(INetworkSerializable)}, tag memcpyable struct with {nameof(INetworkSerializeByMemcpy)}, or add an extension method for {nameof(FastBufferWriter)}.{k_WriteValueMethodName} to define serialization.");
continue; continue;
} }

View File

@@ -107,7 +107,15 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
foreach (var methodDefinition in typeDefinition.Methods) foreach (var methodDefinition in typeDefinition.Methods)
{ {
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegates)) if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesEnum))
{
methodDefinition.IsPublic = true;
}
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesStruct))
{
methodDefinition.IsPublic = true;
}
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable))
{ {
methodDefinition.IsPublic = true; methodDefinition.IsPublic = true;
} }

View File

@@ -7,6 +7,7 @@
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"
], ],
"excludePlatforms": [],
"allowUnsafeCode": true, "allowUnsafeCode": true,
"overrideReferences": true, "overrideReferences": true,
"precompiledReferences": [ "precompiledReferences": [
@@ -15,5 +16,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
}

View File

@@ -1,23 +0,0 @@
using Unity.Netcode.Components;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor
{
[CustomEditor(typeof(NetworkAnimator), true)]
[CanEditMultipleObjects]
public class NetworkAnimatorEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
var label = new GUIContent("Animator", "The Animator component to synchronize");
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Animator"), label);
EditorGUI.EndChangeCheck();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -211,5 +211,91 @@ namespace Unity.Netcode.Editor
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck(); EditorGUI.EndChangeCheck();
} }
/// <summary>
/// Invoked once when a NetworkBehaviour component is
/// displayed in the inspector view.
/// </summary>
private void OnEnable()
{
// When we first add a NetworkBehaviour this editor will be enabled
// so we go ahead and check for an already existing NetworkObject here
CheckForNetworkObject((target as NetworkBehaviour).gameObject);
}
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
public static Transform GetRootParentTransform(Transform transform)
{
if (transform.parent == null || transform.parent == transform)
{
return transform;
}
return GetRootParentTransform(transform.parent);
}
/// <summary>
/// Used to determine if a GameObject has one or more NetworkBehaviours but
/// does not already have a NetworkObject component. If not it will notify
/// the user that NetworkBehaviours require a NetworkObject.
/// </summary>
public static void CheckForNetworkObject(GameObject gameObject, bool networkObjectRemoved = false)
{
// If there are no NetworkBehaviours or no gameObject, then exit early
if (gameObject == null || (gameObject.GetComponent<NetworkBehaviour>() == null && gameObject.GetComponentInChildren<NetworkBehaviour>() == null))
{
return;
}
// Now get the root parent transform to the current GameObject (or itself)
var rootTransform = GetRootParentTransform(gameObject.transform);
// 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.
var networkObject = rootTransform.GetComponent<NetworkObject>();
if (networkObject == null)
{
networkObject = rootTransform.GetComponentInChildren<NetworkObject>();
if (networkObject == null)
{
// If we are removing a NetworkObject but there is still one or more NetworkBehaviour components
// and the user has already turned "Auto-Add NetworkObject" on when first notified about the requirement
// then just send a reminder to the user why the NetworkObject they just deleted seemingly "re-appeared"
// again.
if (networkObjectRemoved && EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists) && EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists))
{
Debug.LogWarning($"{gameObject.name} still has {nameof(NetworkBehaviour)}s and Auto-Add NetworkObjects is enabled. A NetworkObject is being added back to {gameObject.name}.");
Debug.Log($"To reset Auto-Add NetworkObjects: Select the Netcode->General->Reset Auto-Add NetworkObject menu item.");
}
// Notify and provide the option to add it one time, always add a NetworkObject, or do nothing and let the user manually add it
if (EditorUtility.DisplayDialog($"{nameof(NetworkBehaviour)}s require a {nameof(NetworkObject)}",
$"{gameObject.name} does not have a {nameof(NetworkObject)} component. Would you like to add one now?", "Yes", "No (manually add it)",
DialogOptOutDecisionType.ForThisMachine, AutoAddNetworkObjectIfNoneExists))
{
gameObject.AddComponent<NetworkObject>();
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(activeScene);
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene);
}
}
}
}
/// <summary>
/// This allows users to reset the Auto-Add NetworkObject preference
/// so the next time they add a NetworkBehaviour to a GameObject without
/// a NetworkObject it will display the dialog box again and not
/// automatically add a NetworkObject.
/// </summary>
[MenuItem("Netcode/General/Reset Auto-Add NetworkObject", false, 1)]
private static void ResetMultiplayerToolsTipStatus()
{
if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists))
{
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, false);
}
}
} }
} }

View File

@@ -135,9 +135,13 @@ namespace Unity.Netcode.Editor
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true); m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true);
m_NetworkPrefabsList.elementHeightCallback = index => m_NetworkPrefabsList.elementHeightCallback = index =>
{ {
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index); var networkOverrideInt = 0;
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override)); if (m_NetworkPrefabsList.count > 0)
var networkOverrideInt = networkOverrideProp.enumValueIndex; {
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
networkOverrideInt = networkOverrideProp.enumValueIndex;
}
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5); return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5);
}; };
@@ -359,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)

View File

@@ -1,5 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEditor; using UnityEditor;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
@@ -25,7 +27,6 @@ namespace Unity.Netcode.Editor
{ {
Singleton = new NetworkManagerHelper(); Singleton = new NetworkManagerHelper();
NetworkManager.NetworkManagerHelper = Singleton; NetworkManager.NetworkManagerHelper = Singleton;
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged; EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged; EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
@@ -40,20 +41,106 @@ namespace Unity.Netcode.Editor
case PlayModeStateChange.ExitingEditMode: case PlayModeStateChange.ExitingEditMode:
{ {
s_LastKnownNetworkManagerParents.Clear(); s_LastKnownNetworkManagerParents.Clear();
ScenesInBuildActiveSceneCheck();
break; break;
} }
} }
} }
/// <summary>
/// Detects if a user is trying to enter into play mode when both conditions are true:
/// - the currently active and open scene is not added to the scenes in build list
/// - an instance of a NetworkManager with scene management enabled can be found
/// If both conditions are met then the user is presented with a dialog box that
/// provides the user with the option to add the scene to the scenes in build list
/// before entering into play mode or the user can continue under those conditions.
///
/// ** When scene management is enabled the user should treat all scenes that need to
/// be synchronized using network scene management as if they were preparing for a build.
/// Any scene that needs to be loaded at run time has to be included in the scenes in
/// build list. **
/// </summary>
private static void ScenesInBuildActiveSceneCheck()
{
var scenesList = EditorBuildSettings.scenes.ToList();
var activeScene = SceneManager.GetActiveScene();
var isSceneInBuildSettings = scenesList.Where((c) => c.path == activeScene.path).Count() == 1;
var networkManager = Object.FindObjectOfType<NetworkManager>();
if (!isSceneInBuildSettings && networkManager != null)
{
if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement)
{
if (EditorUtility.DisplayDialog("Add Scene to Scenes in Build", $"The current scene was not found in the scenes" +
$" in build and a {nameof(NetworkManager)} instance was found with scene management enabled! Clients will not be able " +
$"to synchronize to this scene unless it is added to the scenes in build list.\n\nWould you like to add it now?",
"Yes", "No - Continue"))
{
scenesList.Add(new EditorBuildSettingsScene(activeScene.path, true));
EditorBuildSettings.scenes = scenesList.ToArray();
}
}
}
}
/// <summary>
/// Invoked only when the hierarchy changes
/// </summary>
private static void EditorApplication_hierarchyChanged() private static void EditorApplication_hierarchyChanged()
{ {
var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>(); var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>();
foreach (var networkManager in allNetworkManagers) foreach (var networkManager in allNetworkManagers)
{ {
networkManager.NetworkManagerCheckForParent(); if (!networkManager.NetworkManagerCheckForParent())
{
Singleton.CheckAndNotifyUserNetworkObjectRemoved(networkManager);
}
} }
} }
/// <summary>
/// Handles notifying users that they cannot add a NetworkObject component
/// to a GameObject that also has a NetworkManager component. The NetworkObject
/// will always be removed.
/// GameObject + NetworkObject then NetworkManager = NetworkObject removed
/// GameObject + NetworkManager then NetworkObject = NetworkObject removed
/// Note: Since this is always invoked after <see cref="NetworkManagerCheckForParent"/>
/// we do not need to check for parent when searching for a NetworkObject component
/// </summary>
public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false)
{
// Check for any NetworkObject at the same gameObject relative layer
var networkObject = networkManager.gameObject.GetComponent<NetworkObject>();
if (networkObject == null)
{
// if none is found, check to see if any children have a NetworkObject
networkObject = networkManager.gameObject.GetComponentInChildren<NetworkObject>();
if (networkObject == null)
{
return;
}
}
if (!EditorApplication.isUpdating)
{
Object.DestroyImmediate(networkObject);
if (!EditorApplication.isPlaying && !editorTest)
{
EditorUtility.DisplayDialog($"Removing {nameof(NetworkObject)}", NetworkManagerAndNetworkObjectNotAllowedMessage(), "OK");
}
else
{
Debug.LogError(NetworkManagerAndNetworkObjectNotAllowedMessage());
}
}
}
public string NetworkManagerAndNetworkObjectNotAllowedMessage()
{
return $"A {nameof(GameObject)} cannot have both a {nameof(NetworkManager)} and {nameof(NetworkObject)} assigned to it or any children under it.";
}
/// <summary> /// <summary>
/// Handles notifying the user, via display dialog window, that they have nested a NetworkManager. /// Handles notifying the user, via display dialog window, that they have nested a NetworkManager.
/// When in edit mode it provides the option to automatically fix the issue /// When in edit mode it provides the option to automatically fix the issue

View File

@@ -100,5 +100,32 @@ namespace Unity.Netcode.Editor
GUI.enabled = guiEnabled; GUI.enabled = guiEnabled;
} }
} }
// Saved for use in OnDestroy
private GameObject m_GameObject;
/// <summary>
/// Invoked once when a NetworkObject component is
/// displayed in the inspector view.
/// </summary>
private void OnEnable()
{
// We set the GameObject upon being enabled because when the
// NetworkObject component is removed (i.e. when OnDestroy is invoked)
// it is no longer valid/available.
m_GameObject = (target as NetworkObject).gameObject;
}
/// <summary>
/// Invoked once when a NetworkObject component is
/// no longer displayed in the inspector view.
/// </summary>
private void OnDestroy()
{
// Since this is also invoked when a NetworkObject component is removed
// from a GameObject, we go ahead and check for a NetworkObject when
// this custom editor is destroyed.
NetworkBehaviourEditor.CheckForNetworkObject(m_GameObject, true);
}
} }
} }

View File

@@ -4,7 +4,7 @@ using Unity.Netcode.Components;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
[CustomEditor(typeof(NetworkTransform))] [CustomEditor(typeof(NetworkTransform), true)]
public class NetworkTransformEditor : UnityEditor.Editor public class NetworkTransformEditor : UnityEditor.Editor
{ {
private SerializedProperty m_SyncPositionXProperty; private SerializedProperty m_SyncPositionXProperty;
@@ -112,6 +112,7 @@ namespace Unity.Netcode.Editor
EditorGUILayout.PropertyField(m_InLocalSpaceProperty); EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
EditorGUILayout.PropertyField(m_InterpolateProperty); EditorGUILayout.PropertyField(m_InterpolateProperty);
#if COM_UNITY_MODULES_PHYSICS
// if rigidbody is present but network rigidbody is not present // if rigidbody is present but network rigidbody is not present
var go = ((NetworkTransform)target).gameObject; var go = ((NetworkTransform)target).gameObject;
if (go.TryGetComponent<Rigidbody>(out _) && go.TryGetComponent<NetworkRigidbody>(out _) == false) if (go.TryGetComponent<Rigidbody>(out _) && go.TryGetComponent<NetworkRigidbody>(out _) == false)
@@ -119,12 +120,15 @@ namespace Unity.Netcode.Editor
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" + EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" +
"Add a NetworkRigidbody component to improve Rigidbody synchronization.", MessageType.Warning); "Add a NetworkRigidbody component to improve Rigidbody synchronization.", MessageType.Warning);
} }
#endif // COM_UNITY_MODULES_PHYSICS
#if COM_UNITY_MODULES_PHYSICS2D
if (go.TryGetComponent<Rigidbody2D>(out _) && go.TryGetComponent<NetworkRigidbody2D>(out _) == false) if (go.TryGetComponent<Rigidbody2D>(out _) && go.TryGetComponent<NetworkRigidbody2D>(out _) == false)
{ {
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody2D but no NetworkRigidbody2D.\n" + EditorGUILayout.HelpBox("This GameObject contains a Rigidbody2D but no NetworkRigidbody2D.\n" +
"Add a NetworkRigidbody2D component to improve Rigidbody2D synchronization.", MessageType.Warning); "Add a NetworkRigidbody2D component to improve Rigidbody2D synchronization.", MessageType.Warning);
} }
#endif // COM_UNITY_MODULES_PHYSICS2D
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 3106ae882c6ec416d855a44c97eeaeef guid: a325130169714440ba1b4878082e8956
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@@ -0,0 +1,53 @@
#if COM_UNITY_NETCODE_ADAPTER_UTP
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
namespace Unity.Netcode.Editor.PackageChecker
{
[InitializeOnLoad]
internal class UTPAdapterChecker
{
private const string k_UTPAdapterPackageName = "com.unity.netcode.adapter.utp";
private static ListRequest s_ListRequest = null;
static UTPAdapterChecker()
{
if (s_ListRequest == null)
{
s_ListRequest = Client.List();
EditorApplication.update += EditorUpdate;
}
}
private static void EditorUpdate()
{
if (!s_ListRequest.IsCompleted)
{
return;
}
EditorApplication.update -= EditorUpdate;
if (s_ListRequest.Status == StatusCode.Success)
{
if (s_ListRequest.Result.Any(p => p.name == k_UTPAdapterPackageName))
{
Debug.Log($"({nameof(UTPAdapterChecker)}) Found UTP Adapter package, it is no longer needed, `UnityTransport` is now directly integrated into the SDK therefore removing it from the project.");
Client.Remove(k_UTPAdapterPackageName);
}
}
else
{
var error = s_ListRequest.Error;
Debug.LogError($"({nameof(UTPAdapterChecker)}) Cannot check the list of packages -> error #{error.errorCode}: {error.message}");
}
s_ListRequest = null;
}
}
}
#endif // COM_UNITY_NETCODE_ADAPTER_UTP

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: bd9e1475e8c8e4a6d935fe2409e3bd26 guid: df5ed97df956b4aad91a221ba59fa304
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,14 @@
{
"name": "Unity.Netcode.Editor.PackageChecker",
"rootNamespace": "Unity.Netcode.Editor.PackageChecker",
"includePlatforms": [
"Editor"
],
"versionDefines": [
{
"name": "com.unity.netcode.adapter.utp",
"expression": "",
"define": "COM_UNITY_NETCODE_ADAPTER_UTP"
}
]
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 78ac2a8d1365141f68da5d0a9e10dbc6 guid: de64d7f9ca85d4bf59c8c24738bc1057
AssemblyDefinitionImporter: AssemblyDefinitionImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

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

View File

@@ -5,10 +5,10 @@ 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")]
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] [assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")] [assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")] [assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]

View File

@@ -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,29 +140,16 @@ 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.
/// </summary> /// </summary>
public bool EnableNetworkLogs = true; public bool EnableNetworkLogs = true;
/// <summary>
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
/// </summary>
public bool UseSnapshotDelta { get; internal set; } = false;
/// <summary>
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
/// </summary>
public bool UseSnapshotSpawn { get; internal set; } = false;
/// <summary>
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
/// </summary>
public int SnapshotMaxSpawnUsage { get; } = 1000;
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one) public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets) public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
/// <summary> /// <summary>

View File

@@ -20,6 +20,17 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// The NetworkObject's owned by this Client /// The NetworkObject's owned by this Client
/// </summary> /// </summary>
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>(); public List<NetworkObject> OwnedObjects
{
get
{
if (PlayerObject != null && PlayerObject.NetworkManager != null && PlayerObject.NetworkManager.IsListening)
{
return PlayerObject.NetworkManager.SpawnManager.GetClientOwnedObjects(ClientId);
}
return new List<NetworkObject>();
}
}
} }
} }

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fda4c0eb89644fcea5416bbf98ea0ba0
timeCreated: 1649966562

View File

@@ -1,388 +0,0 @@
using UnityEngine;
namespace Unity.Netcode
{
internal struct IndexAllocatorEntry
{
internal int Pos; // Position where the memory of this slot is
internal int Length; // Length of the memory allocated to this slot
internal int Next; // Next and Prev define the order of the slots in the buffer
internal int Prev;
internal bool Free; // Whether this is a free slot
}
internal class IndexAllocator
{
private const int k_NotSet = -1;
private readonly int m_MaxSlot; // Maximum number of sections (free or not) in the buffer
private readonly int m_BufferSize; // Size of the buffer we allocated into
private int m_LastSlot = 0; // Last allocated slot
private IndexAllocatorEntry[] m_Slots; // Array of slots
private int[] m_IndexToSlot; // Mapping from the client's index to the slot index
internal IndexAllocator(int bufferSize, int maxSlot)
{
m_MaxSlot = maxSlot;
m_BufferSize = bufferSize;
m_Slots = new IndexAllocatorEntry[m_MaxSlot];
m_IndexToSlot = new int[m_MaxSlot];
Reset();
}
/// <summary>
/// Reset this IndexAllocator to an empty one, with the same sized buffer and slots
/// </summary>
internal void Reset()
{
// todo: could be made faster, for example by having a last index
// and not needing valid stuff past it
for (int i = 0; i < m_MaxSlot; i++)
{
m_Slots[i].Free = true;
m_Slots[i].Next = i + 1;
m_Slots[i].Prev = i - 1;
m_Slots[i].Pos = m_BufferSize;
m_Slots[i].Length = 0;
m_IndexToSlot[i] = k_NotSet;
}
m_Slots[0].Pos = 0;
m_Slots[0].Length = m_BufferSize;
m_Slots[0].Prev = k_NotSet;
m_Slots[m_MaxSlot - 1].Next = k_NotSet;
}
/// <summary>
/// Returns the amount of memory used
/// </summary>
/// <returns>
/// Returns the amount of memory used, starting at 0, ending after the last used slot
/// </returns>
internal int Range
{
get
{
// when the whole buffer is free, m_LastSlot points to an empty slot
if (m_Slots[m_LastSlot].Free)
{
return 0;
}
// otherwise return the end of the last slot used
return m_Slots[m_LastSlot].Pos + m_Slots[m_LastSlot].Length;
}
}
/// <summary>
/// Allocate a slot with "size" position, for index "index"
/// </summary>
/// <param name="index">The client index to identify this. Used in Deallocate to identify which slot</param>
/// <param name="size">The size required. </param>
/// <param name="pos">Returns the position to use in the buffer </param>
/// <returns>
/// true if successful, false is there isn't enough memory available or no slots are large enough
/// </returns>
internal bool Allocate(int index, int size, out int pos)
{
pos = 0;
// size must be positive, index must be within range
if (size < 0 || index < 0 || index >= m_MaxSlot)
{
return false;
}
// refuse allocation if the index is already in use
if (m_IndexToSlot[index] != k_NotSet)
{
return false;
}
// todo: this is the slowest part
// improvement 1: list of free blocks (minor)
// improvement 2: heap of free blocks
for (int i = 0; i < m_MaxSlot; i++)
{
if (m_Slots[i].Free && m_Slots[i].Length >= size)
{
m_IndexToSlot[index] = i;
int leftOver = m_Slots[i].Length - size;
int next = m_Slots[i].Next;
if (m_Slots[next].Free)
{
m_Slots[next].Pos -= leftOver;
m_Slots[next].Length += leftOver;
}
else
{
int add = MoveSlotAfter(i);
m_Slots[add].Pos = m_Slots[i].Pos + size;
m_Slots[add].Length = m_Slots[i].Length - size;
}
m_Slots[i].Free = false;
m_Slots[i].Length = size;
pos = m_Slots[i].Pos;
// if we allocate past the current range, we are the last slot
if (m_Slots[i].Pos + m_Slots[i].Length > Range)
{
m_LastSlot = i;
}
return true;
}
}
return false;
}
/// <summary>
/// Deallocate a slot
/// </summary>
/// <param name="index">The client index to identify this. Same index used in Allocate </param>
/// <returns>
/// true if successful, false is there isn't an allocated slot at this index
/// </returns>
internal bool Deallocate(int index)
{
// size must be positive, index must be within range
if (index < 0 || index >= m_MaxSlot)
{
return false;
}
int slot = m_IndexToSlot[index];
if (slot == k_NotSet)
{
return false;
}
if (m_Slots[slot].Free)
{
return false;
}
m_Slots[slot].Free = true;
int prev = m_Slots[slot].Prev;
int next = m_Slots[slot].Next;
// if previous slot was free, merge and grow
if (prev != k_NotSet && m_Slots[prev].Free)
{
m_Slots[prev].Length += m_Slots[slot].Length;
m_Slots[slot].Length = 0;
// if the slot we're merging was the last one, the last one is now the one we merged with
if (slot == m_LastSlot)
{
m_LastSlot = prev;
}
// todo: verify what this does on full or nearly full cases
MoveSlotToEnd(slot);
slot = prev;
}
next = m_Slots[slot].Next;
// merge with next slot if it is free
if (next != k_NotSet && m_Slots[next].Free)
{
m_Slots[slot].Length += m_Slots[next].Length;
m_Slots[next].Length = 0;
MoveSlotToEnd(next);
}
// if we just deallocate the last one, we need to move last back
if (slot == m_LastSlot)
{
m_LastSlot = m_Slots[m_LastSlot].Prev;
// if there's nothing allocated anymore, use 0
if (m_LastSlot == k_NotSet)
{
m_LastSlot = 0;
}
}
// mark the index as available
m_IndexToSlot[index] = k_NotSet;
return true;
}
// Take a slot at the end and link it to go just after "slot".
// Used when allocating part of a slot and we need an entry for the rest
// Returns the slot that was picked
private int MoveSlotAfter(int slot)
{
int ret = m_Slots[m_MaxSlot - 1].Prev;
int p0 = m_Slots[ret].Prev;
m_Slots[p0].Next = m_MaxSlot - 1;
m_Slots[m_MaxSlot - 1].Prev = p0;
int p1 = m_Slots[slot].Next;
m_Slots[slot].Next = ret;
m_Slots[p1].Prev = ret;
m_Slots[ret].Prev = slot;
m_Slots[ret].Next = p1;
return ret;
}
// Move the slot "slot" to the end of the list.
// Used when merging two slots, that gives us an extra entry at the end
private void MoveSlotToEnd(int slot)
{
// if we're already there
if (m_Slots[slot].Next == k_NotSet)
{
return;
}
int prev = m_Slots[slot].Prev;
int next = m_Slots[slot].Next;
m_Slots[prev].Next = next;
if (next != k_NotSet)
{
m_Slots[next].Prev = prev;
}
int p0 = m_Slots[m_MaxSlot - 1].Prev;
m_Slots[p0].Next = slot;
m_Slots[slot].Next = m_MaxSlot - 1;
m_Slots[m_MaxSlot - 1].Prev = slot;
m_Slots[slot].Prev = p0;
m_Slots[slot].Pos = m_BufferSize;
}
// runs a bunch of consistency check on the Allocator
internal bool Verify()
{
int pos = k_NotSet;
int count = 0;
int total = 0;
int endPos = 0;
do
{
int prev = pos;
if (pos != k_NotSet)
{
pos = m_Slots[pos].Next;
if (pos == k_NotSet)
{
break;
}
}
else
{
pos = 0;
}
if (m_Slots[pos].Prev != prev)
{
// the previous is not correct
return false;
}
if (m_Slots[pos].Length < 0)
{
// Length should be positive
return false;
}
if (prev != k_NotSet && m_Slots[prev].Free && m_Slots[pos].Free && m_Slots[pos].Length > 0)
{
// should not have two consecutive free slots
return false;
}
if (m_Slots[pos].Pos != total)
{
// slots should all line up nicely
return false;
}
if (!m_Slots[pos].Free)
{
endPos = m_Slots[pos].Pos + m_Slots[pos].Length;
}
total += m_Slots[pos].Length;
count++;
} while (pos != k_NotSet);
if (count != m_MaxSlot)
{
// some slots were lost
return false;
}
if (total != m_BufferSize)
{
// total buffer should be accounted for
return false;
}
if (endPos != Range)
{
// end position should match reported end position
return false;
}
return true;
}
// Debug display the allocator structure
internal void DebugDisplay()
{
string logMessage = "IndexAllocator structure\n";
bool[] seen = new bool[m_MaxSlot];
int pos = 0;
int count = 0;
bool prevEmpty = false;
do
{
seen[pos] = true;
count++;
if (m_Slots[pos].Length == 0 && prevEmpty)
{
// don't display repetitive empty slots
}
else
{
logMessage += string.Format("{0}:{1}, {2} ({3}) \n", m_Slots[pos].Pos, m_Slots[pos].Length,
m_Slots[pos].Free ? "Free" : "Used", pos);
if (m_Slots[pos].Length == 0)
{
prevEmpty = true;
}
else
{
prevEmpty = false;
}
}
pos = m_Slots[pos].Next;
} while (pos != k_NotSet && !seen[pos]);
logMessage += string.Format("{0} Total entries\n", count);
Debug.Log(logMessage);
}
}
}

View File

@@ -158,37 +158,57 @@ namespace Unity.Netcode
// We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC // We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC
// to ourself. Sadly we have to figure that out from the list of clientIds :( // to ourself. Sadly we have to figure that out from the list of clientIds :(
bool shouldSendToHost = false; bool shouldSendToHost = false;
if (clientRpcParams.Send.TargetClientIds != null) if (clientRpcParams.Send.TargetClientIds != null)
{ {
foreach (var clientId in clientRpcParams.Send.TargetClientIds) foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
{ {
if (clientId == NetworkManager.ServerClientId) if (targetClientId == NetworkManager.ServerClientId)
{ {
shouldSendToHost = true; shouldSendToHost = true;
break; break;
} }
// Check to make sure we are sending to only observers, if not log an error.
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
{
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
}
} }
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
} }
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null) else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
{ {
foreach (var clientId in clientRpcParams.Send.TargetClientIdsNativeArray) foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
{ {
if (clientId == NetworkManager.ServerClientId) if (targetClientId == NetworkManager.ServerClientId)
{ {
shouldSendToHost = true; shouldSendToHost = true;
break; break;
} }
// Check to make sure we are sending to only observers, if not log an error.
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
{
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
}
} }
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value); rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
} }
else else
{ {
shouldSendToHost = IsHost; var observerEnumerator = NetworkObject.Observers.GetEnumerator();
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ConnectedClientsIds); while (observerEnumerator.MoveNext())
{
// Skip over the host
if (IsHost && observerEnumerator.Current == NetworkManager.LocalClientId)
{
shouldSendToHost = true;
continue;
}
rpcWriteSize = NetworkManager.MessagingSystem.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current);
}
} }
// If we are a server/host then we just no op and send to ourself // If we are a server/host then we just no op and send to ourself
@@ -228,6 +248,12 @@ namespace Unity.Netcode
#endif #endif
} }
internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ulong targetClientId)
{
var containerNameHoldingId = clientRpcParams.Send.TargetClientIds != null ? nameof(ClientRpcParams.Send.TargetClientIds) : nameof(ClientRpcParams.Send.TargetClientIdsNativeArray);
return $"Sending ClientRpc to non-observer! {containerNameHoldingId} contains clientId {targetClientId} that is not an observer!";
}
/// <summary> /// <summary>
/// Gets the NetworkManager that owns this NetworkBehaviour instance /// Gets the NetworkManager that owns this NetworkBehaviour instance
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized /// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
@@ -235,42 +261,42 @@ namespace Unity.Netcode
public NetworkManager NetworkManager => NetworkObject.NetworkManager; public NetworkManager NetworkManager => NetworkObject.NetworkManager;
/// <summary> /// <summary>
/// Gets if the object is the the personal clients player object /// If a NetworkObject is assigned, it will return whether or not this NetworkObject
/// is the local player object. If no NetworkObject is assigned it will always return false.
/// </summary> /// </summary>
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer; public bool IsLocalPlayer { get; private set; }
/// <summary> /// <summary>
/// Gets if the object is owned by the local player or if the object is the local player object /// Gets if the object is owned by the local player or if the object is the local player object
/// </summary> /// </summary>
public bool IsOwner => NetworkObject.IsOwner; public bool IsOwner { get; internal set; }
/// <summary> /// <summary>
/// Gets if we are executing as server /// Gets if we are executing as server
/// </summary> /// </summary>
protected bool IsServer => IsRunning && NetworkManager.IsServer; protected bool IsServer { get; private set; }
/// <summary> /// <summary>
/// Gets if we are executing as client /// Gets if we are executing as client
/// </summary> /// </summary>
protected bool IsClient => IsRunning && NetworkManager.IsClient; protected bool IsClient { get; private set; }
/// <summary> /// <summary>
/// Gets if we are executing as Host, I.E Server and Client /// Gets if we are executing as Host, I.E Server and Client
/// </summary> /// </summary>
protected bool IsHost => IsRunning && NetworkManager.IsHost; protected bool IsHost { get; private set; }
private bool IsRunning => NetworkManager && NetworkManager.IsListening;
/// <summary> /// <summary>
/// Gets Whether or not the object has a owner /// Gets Whether or not the object has a owner
/// </summary> /// </summary>
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer; public bool IsOwnedByServer { get; internal set; }
/// <summary> /// <summary>
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component /// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate /// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate
/// </summary> /// </summary>
public bool IsSpawned => HasNetworkObject ? NetworkObject.IsSpawned : false; public bool IsSpawned { get; internal set; }
internal bool IsBehaviourEditable() internal bool IsBehaviourEditable()
{ {
@@ -327,12 +353,12 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour /// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
/// </summary> /// </summary>
public ulong NetworkObjectId => NetworkObject.NetworkObjectId; public ulong NetworkObjectId { get; internal set; }
/// <summary> /// <summary>
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject /// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
/// </summary> /// </summary>
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this); public ushort NetworkBehaviourId { get; internal set; }
/// <summary> /// <summary>
/// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster /// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
@@ -352,7 +378,47 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets the ClientId that owns the NetworkObject /// Gets the ClientId that owns the NetworkObject
/// </summary> /// </summary>
public ulong OwnerClientId => NetworkObject.OwnerClientId; public ulong OwnerClientId { get; internal set; }
/// <summary>
/// Updates properties with network session related
/// dependencies such as a NetworkObject's spawned
/// state or NetworkManager's session state.
/// </summary>
internal void UpdateNetworkProperties()
{
// Set NetworkObject dependent properties
if (NetworkObject != null)
{
// Set identification related properties
NetworkObjectId = NetworkObject.NetworkObjectId;
IsLocalPlayer = NetworkObject.IsLocalPlayer;
// This is "OK" because GetNetworkBehaviourOrderIndex uses the order of
// NetworkObject.ChildNetworkBehaviours which is set once when first
// accessed.
NetworkBehaviourId = NetworkObject.GetNetworkBehaviourOrderIndex(this);
// Set ownership related properties
IsOwnedByServer = NetworkObject.IsOwnedByServer;
IsOwner = NetworkObject.IsOwner;
OwnerClientId = NetworkObject.OwnerClientId;
// Set NetworkManager dependent properties
if (NetworkManager != null)
{
IsHost = NetworkManager.IsListening && NetworkManager.IsHost;
IsClient = NetworkManager.IsListening && NetworkManager.IsClient;
IsServer = NetworkManager.IsListening && NetworkManager.IsServer;
}
}
else // Shouldn't happen, but if so then set the properties to their default value;
{
OwnerClientId = NetworkObjectId = default;
IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = default;
NetworkBehaviourId = default;
}
}
/// <summary> /// <summary>
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup. /// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
@@ -366,21 +432,41 @@ namespace Unity.Netcode
internal void InternalOnNetworkSpawn() internal void InternalOnNetworkSpawn()
{ {
IsSpawned = true;
InitializeVariables(); InitializeVariables();
UpdateNetworkProperties();
OnNetworkSpawn();
} }
internal void InternalOnNetworkDespawn() { } internal void InternalOnNetworkDespawn()
{
IsSpawned = false;
UpdateNetworkProperties();
OnNetworkDespawn();
}
/// <summary> /// <summary>
/// Gets called when the local client gains ownership of this object /// Gets called when the local client gains ownership of this object
/// </summary> /// </summary>
public virtual void OnGainedOwnership() { } public virtual void OnGainedOwnership() { }
internal void InternalOnGainedOwnership()
{
UpdateNetworkProperties();
OnGainedOwnership();
}
/// <summary> /// <summary>
/// Gets called when we loose ownership of this object /// Gets called when we loose ownership of this object
/// </summary> /// </summary>
public virtual void OnLostOwnership() { } public virtual void OnLostOwnership() { }
internal void InternalOnLostOwnership()
{
UpdateNetworkProperties();
OnLostOwnership();
}
/// <summary> /// <summary>
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed /// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
/// </summary> /// </summary>
@@ -433,12 +519,10 @@ namespace Unity.Netcode
m_VarInit = true; m_VarInit = true;
FieldInfo[] sortedFields = GetFieldInfoForType(GetType()); var sortedFields = GetFieldInfoForType(GetType());
for (int i = 0; i < sortedFields.Length; i++) for (int i = 0; i < sortedFields.Length; i++)
{ {
Type fieldType = sortedFields[i].FieldType; var fieldType = sortedFields[i].FieldType;
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase))) if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
{ {
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this); var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
@@ -499,7 +583,7 @@ namespace Unity.Netcode
} }
} }
internal void VariableUpdate(ulong clientId) internal void VariableUpdate(ulong targetClientId)
{ {
if (!m_VarInit) if (!m_VarInit)
{ {
@@ -507,67 +591,58 @@ namespace Unity.Netcode
} }
PreNetworkVariableWrite(); PreNetworkVariableWrite();
NetworkVariableUpdate(clientId, NetworkBehaviourId); NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
} }
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>(); internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>(); internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
private void NetworkVariableUpdate(ulong clientId, int behaviourIndex) private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
{ {
if (!CouldHaveDirtyNetworkVariables()) if (!CouldHaveDirtyNetworkVariables())
{ {
return; return;
} }
if (NetworkManager.NetworkConfig.UseSnapshotDelta) for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
{ {
var shouldSend = false;
for (int k = 0; k < NetworkVariableFields.Count; k++) for (int k = 0; k < NetworkVariableFields.Count; k++)
{ {
NetworkManager.SnapshotSystem.Store(NetworkObjectId, behaviourIndex, k, NetworkVariableFields[k]); var networkVariable = NetworkVariableFields[k];
} if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
}
if (!NetworkManager.NetworkConfig.UseSnapshotDelta)
{
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
{
var shouldSend = false;
for (int k = 0; k < NetworkVariableFields.Count; k++)
{ {
if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer)) shouldSend = true;
break;
}
}
if (shouldSend)
{
var message = new NetworkVariableDeltaMessage
{
NetworkObjectId = NetworkObjectId,
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
NetworkBehaviour = this,
TargetClientId = targetClientId,
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
};
// TODO: Serialization is where the IsDirty flag gets changed.
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
// we still have to actually serialize the message even though we're not sending it, otherwise
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
// so we don't have to do this serialization work if we're not going to use the result.
if (IsServer && targetClientId == NetworkManager.ServerClientId)
{
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
using (tmpWriter)
{ {
shouldSend = true; message.Serialize(tmpWriter);
} }
} }
else
if (shouldSend)
{ {
var message = new NetworkVariableDeltaMessage NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
{
NetworkObjectId = NetworkObjectId,
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
NetworkBehaviour = this,
ClientId = clientId,
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
};
// TODO: Serialization is where the IsDirty flag gets changed.
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
// we still have to actually serialize the message even though we're not sending it, otherwise
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
// so we don't have to do this serialization work if we're not going to use the result.
if (IsServer && clientId == NetworkManager.ServerClientId)
{
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
using (tmpWriter)
{
message.Serialize(tmpWriter);
}
}
else
{
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
}
} }
} }
} }
@@ -595,7 +670,7 @@ namespace Unity.Netcode
} }
} }
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId) internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
{ {
if (NetworkVariableFields.Count == 0) if (NetworkVariableFields.Count == 0)
{ {
@@ -604,7 +679,7 @@ namespace Unity.Netcode
for (int j = 0; j < NetworkVariableFields.Count; j++) for (int j = 0; j < NetworkVariableFields.Count; j++)
{ {
bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId); bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
if (canClientRead) if (canClientRead)
{ {

View File

@@ -57,7 +57,7 @@ namespace Unity.Netcode
{ {
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{ {
sobj.ChildNetworkBehaviours[k].VariableUpdate(networkManager.ServerClientId); sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -54,8 +54,6 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal NetworkManager NetworkManagerOwner; internal NetworkManager NetworkManagerOwner;
private ulong m_NetworkObjectId;
/// <summary> /// <summary>
/// Gets the unique Id of this object that is synced across the network /// Gets the unique Id of this object that is synced across the network
/// </summary> /// </summary>
@@ -64,33 +62,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets the ClientId of the owner of this NetworkObject /// Gets the ClientId of the owner of this NetworkObject
/// </summary> /// </summary>
public ulong OwnerClientId public ulong OwnerClientId { get; internal set; }
{
get
{
if (OwnerClientIdInternal == null)
{
return NetworkManager != null ? NetworkManager.ServerClientId : 0;
}
else
{
return OwnerClientIdInternal.Value;
}
}
internal set
{
if (NetworkManager != null && value == NetworkManager.ServerClientId)
{
OwnerClientIdInternal = null;
}
else
{
OwnerClientIdInternal = value;
}
}
}
internal ulong? OwnerClientIdInternal = null;
/// <summary> /// <summary>
/// If true, the object will always be replicated as root on clients and the parent will be ignored. /// If true, the object will always be replicated as root on clients and the parent will be ignored.
@@ -234,11 +206,6 @@ namespace Unity.Netcode
throw new VisibilityChangeException("The object is already visible"); throw new VisibilityChangeException("The object is already visible");
} }
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
{
SnapshotSpawn(clientId);
}
Observers.Add(clientId); Observers.Add(clientId);
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this); NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
@@ -314,23 +281,15 @@ namespace Unity.Netcode
throw new VisibilityChangeException("Cannot hide an object from the server"); throw new VisibilityChangeException("Cannot hide an object from the server");
} }
Observers.Remove(clientId); Observers.Remove(clientId);
if (NetworkManager.NetworkConfig.UseSnapshotSpawn) var message = new DestroyObjectMessage
{ {
SnapshotDespawn(clientId); NetworkObjectId = NetworkObjectId
} };
else // Send destroy call
{ var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
var message = new DestroyObjectMessage NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
{
NetworkObjectId = NetworkObjectId
};
// Send destroy call
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
}
} }
/// <summary> /// <summary>
@@ -345,14 +304,14 @@ namespace Unity.Netcode
throw new ArgumentNullException("At least one " + nameof(NetworkObject) + " has to be provided"); throw new ArgumentNullException("At least one " + nameof(NetworkObject) + " has to be provided");
} }
NetworkManager networkManager = networkObjects[0].NetworkManager; var networkManager = networkObjects[0].NetworkManager;
if (!networkManager.IsServer) if (!networkManager.IsServer)
{ {
throw new NotServerException("Only server can change visibility"); throw new NotServerException("Only server can change visibility");
} }
if (clientId == networkManager.ServerClientId) if (clientId == NetworkManager.ServerClientId)
{ {
throw new VisibilityChangeException("Cannot hide an object from the server"); throw new VisibilityChangeException("Cannot hide an object from the server");
} }
@@ -384,84 +343,24 @@ namespace Unity.Netcode
private void OnDestroy() private void OnDestroy()
{ {
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned &&
&& (IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true))) (IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
{ {
throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
} }
if (NetworkManager != null && NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) if (NetworkManager != null && NetworkManager.SpawnManager != null &&
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{ {
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false); if (this == networkObject)
{
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false);
}
} }
} }
private SnapshotDespawnCommand GetDespawnCommand()
{
var command = new SnapshotDespawnCommand();
command.NetworkObjectId = NetworkObjectId;
return command;
}
private SnapshotSpawnCommand GetSpawnCommand()
{
var command = new SnapshotSpawnCommand();
command.NetworkObjectId = NetworkObjectId;
command.OwnerClientId = OwnerClientId;
command.IsPlayerObject = IsPlayerObject;
command.IsSceneObject = (IsSceneObject == null) || IsSceneObject.Value;
ulong? parent = NetworkManager.SpawnManager.GetSpawnParentId(this);
if (parent != null)
{
command.ParentNetworkId = parent.Value;
}
else
{
// write own network id, when no parents. todo: optimize this.
command.ParentNetworkId = command.NetworkObjectId;
}
command.GlobalObjectIdHash = HostCheckForGlobalObjectIdHashOverride();
// todo: check if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(clientId)) for any clientId
command.ObjectPosition = transform.position;
command.ObjectRotation = transform.rotation;
command.ObjectScale = transform.localScale;
return command;
}
private void SnapshotSpawn()
{
var command = GetSpawnCommand();
NetworkManager.SnapshotSystem.Spawn(command);
}
private void SnapshotSpawn(ulong clientId)
{
var command = GetSpawnCommand();
command.TargetClientIds = new List<ulong>();
command.TargetClientIds.Add(clientId);
NetworkManager.SnapshotSystem.Spawn(command);
}
internal void SnapshotDespawn()
{
var command = GetDespawnCommand();
NetworkManager.SnapshotSystem.Despawn(command);
}
internal void SnapshotDespawn(ulong clientId)
{
var command = GetDespawnCommand();
command.TargetClientIds = new List<ulong>();
command.TargetClientIds.Add(clientId);
NetworkManager.SnapshotSystem.Despawn(command);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SpawnInternal(bool destroyWithScene, ulong? ownerClientId, bool playerObject) private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
{ {
if (!NetworkManager.IsListening) if (!NetworkManager.IsListening)
{ {
@@ -475,12 +374,6 @@ namespace Unity.Netcode
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene); NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene);
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
{
SnapshotSpawn();
}
ulong ownerId = ownerClientId != null ? ownerClientId.Value : NetworkManager.ServerClientId;
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
{ {
if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId)) if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId))
@@ -496,7 +389,7 @@ namespace Unity.Netcode
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param> /// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
public void Spawn(bool destroyWithScene = false) public void Spawn(bool destroyWithScene = false)
{ {
SpawnInternal(destroyWithScene, null, false); SpawnInternal(destroyWithScene, NetworkManager.ServerClientId, false);
} }
/// <summary> /// <summary>
@@ -547,17 +440,29 @@ namespace Unity.Netcode
internal void InvokeBehaviourOnLostOwnership() internal void InvokeBehaviourOnLostOwnership()
{ {
// Server already handles this earlier, hosts should ignore, all clients should update
if (!NetworkManager.IsServer)
{
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
}
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{ {
ChildNetworkBehaviours[i].OnLostOwnership(); ChildNetworkBehaviours[i].InternalOnLostOwnership();
} }
} }
internal void InvokeBehaviourOnGainedOwnership() internal void InvokeBehaviourOnGainedOwnership()
{ {
// Server already handles this earlier, hosts should ignore and only client owners should update
if (!NetworkManager.IsServer && NetworkManager.LocalClientId == OwnerClientId)
{
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
}
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{ {
ChildNetworkBehaviours[i].OnGainedOwnership(); ChildNetworkBehaviours[i].InternalOnGainedOwnership();
} }
} }
@@ -756,13 +661,7 @@ namespace Unity.Netcode
if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value)) if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
{ {
if (OrphanChildren.Add(this)) OrphanChildren.Add(this);
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkObject)} ({name}) cannot find its parent, added to {nameof(OrphanChildren)} set");
}
}
return false; return false;
} }
@@ -793,19 +692,21 @@ namespace Unity.Netcode
internal void InvokeBehaviourNetworkSpawn() internal void InvokeBehaviourNetworkSpawn()
{ {
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{ {
ChildNetworkBehaviours[i].InternalOnNetworkSpawn(); ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
ChildNetworkBehaviours[i].OnNetworkSpawn();
} }
} }
internal void InvokeBehaviourNetworkDespawn() internal void InvokeBehaviourNetworkDespawn()
{ {
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{ {
ChildNetworkBehaviours[i].InternalOnNetworkDespawn(); ChildNetworkBehaviours[i].InternalOnNetworkDespawn();
ChildNetworkBehaviours[i].OnNetworkDespawn();
} }
} }
@@ -834,13 +735,13 @@ namespace Unity.Netcode
} }
} }
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId) internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
{ {
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{ {
var behavior = ChildNetworkBehaviours[i]; var behavior = ChildNetworkBehaviours[i];
behavior.InitializeVariables(); behavior.InitializeVariables();
behavior.WriteNetworkVariableData(writer, clientId); behavior.WriteNetworkVariableData(writer, targetClientId);
} }
} }
@@ -853,6 +754,27 @@ namespace Unity.Netcode
} }
} }
// NGO currently guarantees that the client will receive spawn data for all objects in one network tick.
// Children may arrive before their parents; when they do they are stored in OrphanedChildren and then
// resolved when their parents arrived. Because we don't send a partial list of spawns (yet), something
// has gone wrong if by the end of an update we still have unresolved orphans
//
// if and when we have different systems for where it is expected that orphans survive across ticks,
// then this warning will remind us that we need to revamp the system because then we can no longer simply
// spawn the orphan without its parent (at least, not when its transform is set to local coords mode)
// - because then youll have children popping at the wrong location not having their parents global position to root them
// - and then theyll pop to the correct location after they get the parent, and that would be not good
internal static void VerifyParentingStatus()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
if (OrphanChildren.Count > 0)
{
NetworkLog.LogWarning($"{nameof(NetworkObject)} ({OrphanChildren.Count}) children not resolved to parents by the end of frame");
}
}
}
internal void SetNetworkVariableData(FastBufferReader reader) internal void SetNetworkVariableData(FastBufferReader reader)
{ {
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
@@ -907,7 +829,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;
@@ -918,7 +840,6 @@ namespace Unity.Netcode
public bool IsSceneObject; public bool IsSceneObject;
public bool HasTransform; public bool HasTransform;
public bool IsReparented; public bool IsReparented;
public bool HasNetworkVariables;
} }
public HeaderData Header; public HeaderData Header;
@@ -927,7 +848,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;
@@ -979,10 +900,7 @@ namespace Unity.Netcode
} }
} }
if (Header.HasNetworkVariables) OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
{
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
}
} }
public unsafe void Deserialize(FastBufferReader reader) public unsafe void Deserialize(FastBufferReader reader)
@@ -1022,7 +940,7 @@ namespace Unity.Netcode
} }
} }
internal SceneObject GetMessageSceneObject(ulong targetClientId, bool includeNetworkVariableData = true) internal SceneObject GetMessageSceneObject(ulong targetClientId)
{ {
var obj = new SceneObject var obj = new SceneObject
{ {
@@ -1033,7 +951,6 @@ namespace Unity.Netcode
OwnerClientId = OwnerClientId, OwnerClientId = OwnerClientId,
IsSceneObject = IsSceneObject ?? true, IsSceneObject = IsSceneObject ?? true,
Hash = HostCheckForGlobalObjectIdHashOverride(), Hash = HostCheckForGlobalObjectIdHashOverride(),
HasNetworkVariables = includeNetworkVariableData
}, },
OwnerObject = this, OwnerObject = this,
TargetClientId = targetClientId TargetClientId = targetClientId

View File

@@ -1,93 +0,0 @@
using System;
namespace Unity.Netcode
{
internal class ConnectionRtt
{
private double[] m_RttSendTimes; // times at which packet were sent for RTT computations
private int[] m_SendSequence; // tick, or other key, at which packets were sent (to allow matching)
private double[] m_MeasuredLatencies; // measured latencies (ring buffer)
private int m_LatenciesBegin = 0; // ring buffer begin
private int m_LatenciesEnd = 0; // ring buffer end
/// <summary>
/// Round-trip-time data
/// </summary>
public struct Rtt
{
public double BestSec; // best RTT
public double AverageSec; // average RTT
public double WorstSec; // worst RTT
public double LastSec; // latest ack'ed RTT
public int SampleCount; // number of contributing samples
}
public ConnectionRtt()
{
m_RttSendTimes = new double[NetworkConfig.RttWindowSize];
m_SendSequence = new int[NetworkConfig.RttWindowSize];
m_MeasuredLatencies = new double[NetworkConfig.RttWindowSize];
}
/// <summary>
/// Returns the Round-trip-time computation for this client
/// </summary>
public Rtt GetRtt()
{
var ret = new Rtt();
var index = m_LatenciesBegin;
double total = 0.0;
ret.BestSec = m_MeasuredLatencies[m_LatenciesBegin];
ret.WorstSec = m_MeasuredLatencies[m_LatenciesBegin];
while (index != m_LatenciesEnd)
{
total += m_MeasuredLatencies[index];
ret.SampleCount++;
ret.BestSec = Math.Min(ret.BestSec, m_MeasuredLatencies[index]);
ret.WorstSec = Math.Max(ret.WorstSec, m_MeasuredLatencies[index]);
index = (index + 1) % NetworkConfig.RttAverageSamples;
}
if (ret.SampleCount != 0)
{
ret.AverageSec = total / ret.SampleCount;
// the latest RTT is one before m_LatenciesEnd
ret.LastSec = m_MeasuredLatencies[(m_LatenciesEnd + (NetworkConfig.RttWindowSize - 1)) % NetworkConfig.RttWindowSize];
}
else
{
ret.AverageSec = 0;
ret.BestSec = 0;
ret.WorstSec = 0;
ret.SampleCount = 0;
ret.LastSec = 0;
}
return ret;
}
internal void NotifySend(int sequence, double timeSec)
{
m_RttSendTimes[sequence % NetworkConfig.RttWindowSize] = timeSec;
m_SendSequence[sequence % NetworkConfig.RttWindowSize] = sequence;
}
internal void NotifyAck(int sequence, double timeSec)
{
// if the same slot was not used by a later send
if (m_SendSequence[sequence % NetworkConfig.RttWindowSize] == sequence)
{
double latency = timeSec - m_RttSendTimes[sequence % NetworkConfig.RttWindowSize];
m_MeasuredLatencies[m_LatenciesEnd] = latency;
m_LatenciesEnd = (m_LatenciesEnd + 1) % NetworkConfig.RttAverageSamples;
if (m_LatenciesEnd == m_LatenciesBegin)
{
m_LatenciesBegin = (m_LatenciesBegin + 1) % NetworkConfig.RttAverageSamples;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,9 +14,9 @@ namespace Unity.Netcode
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel; public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
// internal logging // internal logging
internal static void LogInfo(string message) => Debug.Log($"[Netcode] {message}"); public static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
internal static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}"); public static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
internal static void LogError(string message) => Debug.LogError($"[Netcode] {message}"); public static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
/// <summary> /// <summary>
/// Logs an info log locally and on the server if possible. /// Logs an info log locally and on the server if possible.
@@ -62,9 +62,9 @@ namespace Unity.Netcode
LogType = logType, LogType = logType,
Message = message Message = message
}; };
var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.Singleton.ServerClientId); var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId);
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size); NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size);
} }
} }

View File

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

View 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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ac7f57f7d16a46e2aba65558e873727f
timeCreated: 1649799187

View 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();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7fb73a029c314763a04ebb015a07664d
timeCreated: 1649966331

View File

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

View File

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

View File

@@ -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;
} }
@@ -29,24 +29,33 @@ namespace Unity.Netcode
public void Handle(ref NetworkContext context) public void Handle(ref NetworkContext context)
{ {
var networkManager = (NetworkManager)context.SystemOwner; var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
var originalOwner = networkObject.OwnerClientId;
if (networkObject.OwnerClientId == networkManager.LocalClientId)
{
//We are current owner.
networkObject.InvokeBehaviourOnLostOwnership();
}
networkObject.OwnerClientId = OwnerClientId; networkObject.OwnerClientId = OwnerClientId;
// We are current owner.
if (originalOwner == networkManager.LocalClientId)
{
networkObject.InvokeBehaviourOnLostOwnership();
}
// We are new owner.
if (OwnerClientId == networkManager.LocalClientId) if (OwnerClientId == networkManager.LocalClientId)
{ {
//We are new owner.
networkObject.InvokeBehaviourOnGainedOwnership(); networkObject.InvokeBehaviourOnGainedOwnership();
} }
// For all other clients that are neither the former or current owner, update the behaviours' properties
if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId)
{
for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
{
networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties();
}
}
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
} }
} }

View File

@@ -19,6 +19,11 @@ namespace Unity.Netcode
} }
ObjectInfo.Deserialize(reader); ObjectInfo.Deserialize(reader);
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo.Header.IsSceneObject, ObjectInfo.Header.Hash))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
return false;
}
m_ReceivedNetworkVariableData = reader; m_ReceivedNetworkVariableData = reader;
return true; return true;

View File

@@ -1,6 +1,6 @@
namespace Unity.Netcode namespace Unity.Netcode
{ {
internal struct DestroyObjectMessage : INetworkMessage internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
{ {
public ulong NetworkObjectId; public ulong NetworkObjectId;
@@ -16,7 +16,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;
} }

View File

@@ -16,7 +16,7 @@ namespace Unity.Netcode
public ushort NetworkBehaviourIndex; public ushort NetworkBehaviourIndex;
public HashSet<int> DeliveryMappedNetworkVariableIndex; public HashSet<int> DeliveryMappedNetworkVariableIndex;
public ulong ClientId; public ulong TargetClientId;
public NetworkBehaviour NetworkBehaviour; public NetworkBehaviour NetworkBehaviour;
private FastBufferReader m_ReceivedNetworkVariableData; private FastBufferReader m_ReceivedNetworkVariableData;
@@ -31,9 +31,9 @@ namespace Unity.Netcode
writer.WriteValue(NetworkObjectId); writer.WriteValue(NetworkObjectId);
writer.WriteValue(NetworkBehaviourIndex); writer.WriteValue(NetworkBehaviourIndex);
for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++) for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
{ {
if (!DeliveryMappedNetworkVariableIndex.Contains(k)) if (!DeliveryMappedNetworkVariableIndex.Contains(i))
{ {
// This var does not belong to the currently iterating delivery group. // This var does not belong to the currently iterating delivery group.
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
@@ -48,15 +48,25 @@ namespace Unity.Netcode
continue; continue;
} }
// if I'm dirty AND a client, write (server always has all permissions) var startingSize = writer.Length;
// if I'm dirty AND the server AND the client can read me, send. var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
bool shouldWrite = NetworkBehaviour.NetworkVariableFields[k].ShouldWrite(ClientId, NetworkBehaviour.NetworkManager.IsServer); var shouldWrite = networkVariable.IsDirty() &&
networkVariable.CanClientRead(TargetClientId) &&
(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)
{ {
writer.WriteValueSafe((ushort)0); BytePacker.WriteValueBitPacked(writer, 0);
} }
} }
else else
@@ -68,34 +78,34 @@ namespace Unity.Netcode
{ {
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue); var tempWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter); NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<ushort>() + tmpWriter.Length)) if (!writer.TryBeginWrite(tempWriter.Length))
{ {
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
} }
writer.WriteValue((ushort)tmpWriter.Length); tempWriter.CopyTo(writer);
tmpWriter.CopyTo(writer);
} }
else else
{ {
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(writer); networkVariable.WriteDelta(writer);
} }
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(k)) if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(i))
{ {
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(k); NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(i);
NetworkBehaviour.NetworkVariableIndexesToReset.Add(k); NetworkBehaviour.NetworkVariableIndexesToReset.Add(i);
} }
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
ClientId, TargetClientId,
NetworkBehaviour.NetworkObject, NetworkBehaviour.NetworkObject,
NetworkBehaviour.NetworkVariableFields[k].Name, networkVariable.Name,
NetworkBehaviour.__getTypeName(), NetworkBehaviour.__getTypeName(),
writer.Length); writer.Length - startingSize);
} }
} }
} }
@@ -121,9 +131,9 @@ namespace Unity.Netcode
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject)) if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
{ {
NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
if (behaviour == null) if (networkBehaviour == null)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
@@ -132,13 +142,12 @@ namespace Unity.Netcode
} }
else else
{ {
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++) for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
{ {
ushort varSize = 0; int varSize = 0;
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
m_ReceivedNetworkVariableData.ReadValueSafe(out varSize); ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
if (varSize == 0) if (varSize == 0)
{ {
@@ -154,15 +163,17 @@ namespace Unity.Netcode
} }
} }
if (networkManager.IsServer) var networkVariable = networkBehaviour.NetworkVariableFields[i];
if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId))
{ {
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server // we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{ {
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]"); NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
} }
m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize); m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize);
@@ -176,23 +187,23 @@ namespace Unity.Netcode
//A dummy read COULD be added to the interface for this situation, but it's just being too nice. //A dummy read COULD be added to the interface for this situation, but it's just being too nice.
//This is after all a developer fault. A critical error should be fine. //This is after all a developer fault. A critical error should be fine.
// - TwoTen // - TwoTen
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{ {
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]"); NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
} }
return; return;
} }
int readStartPos = m_ReceivedNetworkVariableData.Position; int readStartPos = m_ReceivedNetworkVariableData.Position;
behaviour.NetworkVariableFields[i].ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer); networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
context.SenderId, context.SenderId,
networkObject, networkObject,
behaviour.NetworkVariableFields[i].Name, networkVariable.Name,
behaviour.__getTypeName(), networkBehaviour.__getTypeName(),
context.MessageSize); context.MessageSize);
@@ -202,7 +213,7 @@ namespace Unity.Netcode
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
} }
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize); m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
@@ -211,7 +222,7 @@ namespace Unity.Netcode
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
NetworkLog.LogWarning($"Var delta read too little. {(readStartPos + varSize) - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); NetworkLog.LogWarning($"Var delta read too little. {readStartPos + varSize - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
} }
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize); m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
@@ -222,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);
} }
} }
} }

View File

@@ -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;
} }

View File

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

View File

@@ -1,160 +0,0 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
namespace Unity.Netcode
{
internal struct SnapshotDataMessage : INetworkMessage
{
public int CurrentTick;
public ushort Sequence;
public ushort Range;
public byte[] SendMainBuffer;
public NativeArray<byte> ReceiveMainBuffer;
public struct AckData
{
public ushort LastReceivedSequence;
public ushort ReceivedSequenceMask;
}
public AckData Ack;
public struct EntryData
{
public ulong NetworkObjectId;
public ushort BehaviourIndex;
public ushort VariableIndex;
public int TickWritten;
public ushort Position;
public ushort Length;
}
public NativeList<EntryData> Entries;
public struct SpawnData
{
public ulong NetworkObjectId;
public uint Hash;
public bool IsSceneObject;
public bool IsPlayerObject;
public ulong OwnerClientId;
public ulong ParentNetworkId;
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
public int TickWritten;
}
public NativeList<SpawnData> Spawns;
public struct DespawnData
{
public ulong NetworkObjectId;
public int TickWritten;
}
public NativeList<DespawnData> Despawns;
public unsafe void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(
FastBufferWriter.GetWriteSize(CurrentTick) +
FastBufferWriter.GetWriteSize(Sequence) +
FastBufferWriter.GetWriteSize(Range) + Range +
FastBufferWriter.GetWriteSize(Ack) +
FastBufferWriter.GetWriteSize<ushort>() +
Entries.Length * sizeof(EntryData) +
FastBufferWriter.GetWriteSize<ushort>() +
Spawns.Length * sizeof(SpawnData) +
FastBufferWriter.GetWriteSize<ushort>() +
Despawns.Length * sizeof(DespawnData)
))
{
throw new OverflowException($"Not enough space to serialize {nameof(SnapshotDataMessage)}");
}
writer.WriteValue(CurrentTick);
writer.WriteValue(Sequence);
writer.WriteValue(Range);
writer.WriteBytes(SendMainBuffer, Range);
writer.WriteValue(Ack);
writer.WriteValue((ushort)Entries.Length);
writer.WriteBytes((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
writer.WriteValue((ushort)Spawns.Length);
writer.WriteBytes((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
writer.WriteValue((ushort)Despawns.Length);
writer.WriteBytes((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
}
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
if (!reader.TryBeginRead(
FastBufferWriter.GetWriteSize(CurrentTick) +
FastBufferWriter.GetWriteSize(Sequence) +
FastBufferWriter.GetWriteSize(Range)
))
{
throw new OverflowException($"Not enough space to deserialize {nameof(SnapshotDataMessage)}");
}
reader.ReadValue(out CurrentTick);
reader.ReadValue(out Sequence);
reader.ReadValue(out Range);
ReceiveMainBuffer = new NativeArray<byte>(Range, Allocator.Temp);
reader.ReadBytesSafe((byte*)ReceiveMainBuffer.GetUnsafePtr(), Range);
reader.ReadValueSafe(out Ack);
reader.ReadValueSafe(out ushort length);
Entries = new NativeList<EntryData>(length, Allocator.Temp) { Length = length };
reader.ReadBytesSafe((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
reader.ReadValueSafe(out length);
Spawns = new NativeList<SpawnData>(length, Allocator.Temp) { Length = length };
reader.ReadBytesSafe((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
reader.ReadValueSafe(out length);
Despawns = new NativeList<DespawnData>(length, Allocator.Temp) { Length = length };
reader.ReadBytesSafe((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
return true;
}
public void Handle(ref NetworkContext context)
{
using (ReceiveMainBuffer)
using (Entries)
using (Spawns)
using (Despawns)
{
var systemOwner = context.SystemOwner;
var senderId = context.SenderId;
if (systemOwner is NetworkManager networkManager)
{
// todo: temporary hack around bug
if (!networkManager.IsServer)
{
senderId = networkManager.ServerClientId;
}
var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this);
}
else
{
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
var snapshotSystem = ownerData.Item1;
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
}
}
}
}
}

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ namespace Unity.Netcode
} }
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300; public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue; public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax;
internal struct MessageWithHandler internal struct MessageWithHandler
{ {
@@ -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;

View File

@@ -87,7 +87,13 @@ namespace Unity.Netcode
void TrackPacketReceived(uint packetCount); void TrackPacketReceived(uint packetCount);
void TrackRttToServer(int rtt); void UpdateRttToServer(int rtt);
void UpdateNetworkObjectsCount(int count);
void UpdateConnectionsCount(int count);
void UpdatePacketLoss(float packetLoss);
void DispatchFrame(); void DispatchFrame();
} }

View File

@@ -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;
} }

View File

@@ -66,7 +66,7 @@ namespace Unity.Netcode
private readonly EventMetric<SceneEventMetric> m_SceneEventSentEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventSent.Id); private readonly EventMetric<SceneEventMetric> m_SceneEventSentEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventSent.Id);
private readonly EventMetric<SceneEventMetric> m_SceneEventReceivedEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventReceived.Id); private readonly EventMetric<SceneEventMetric> m_SceneEventReceivedEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventReceived.Id);
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
private readonly Counter m_PacketSentCounter = new Counter(NetworkMetricTypes.PacketsSent.Id) private readonly Counter m_PacketSentCounter = new Counter(NetworkMetricTypes.PacketsSent.Id)
{ {
ShouldResetOnDispatch = true, ShouldResetOnDispatch = true,
@@ -79,6 +79,15 @@ namespace Unity.Netcode
{ {
ShouldResetOnDispatch = true, ShouldResetOnDispatch = true,
}; };
private readonly Gauge m_NetworkObjectsGauge = new Gauge(NetworkMetricTypes.NetworkObjects.Id)
{
ShouldResetOnDispatch = true,
};
private readonly Gauge m_ConnectionsGauge = new Gauge(NetworkMetricTypes.ConnectedClients.Id)
{
ShouldResetOnDispatch = true,
};
private readonly Gauge m_PacketLossGauge = new Gauge(NetworkMetricTypes.PacketLoss.Id);
#endif #endif
private ulong m_NumberOfMetricsThisFrame; private ulong m_NumberOfMetricsThisFrame;
@@ -97,9 +106,12 @@ namespace Unity.Netcode
.WithMetricEvents(m_RpcSentEvent, m_RpcReceivedEvent) .WithMetricEvents(m_RpcSentEvent, m_RpcReceivedEvent)
.WithMetricEvents(m_ServerLogSentEvent, m_ServerLogReceivedEvent) .WithMetricEvents(m_ServerLogSentEvent, m_ServerLogReceivedEvent)
.WithMetricEvents(m_SceneEventSentEvent, m_SceneEventReceivedEvent) .WithMetricEvents(m_SceneEventSentEvent, m_SceneEventReceivedEvent)
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
.WithCounters(m_PacketSentCounter, m_PacketReceivedCounter) .WithCounters(m_PacketSentCounter, m_PacketReceivedCounter)
.WithGauges(m_RttToServerGauge) .WithGauges(m_RttToServerGauge)
.WithGauges(m_NetworkObjectsGauge)
.WithGauges(m_ConnectionsGauge)
.WithGauges(m_PacketLossGauge)
#endif #endif
.Build(); .Build();
@@ -428,7 +440,7 @@ namespace Unity.Netcode
public void TrackPacketSent(uint packetCount) public void TrackPacketSent(uint packetCount)
{ {
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
if (!CanSendMetrics) if (!CanSendMetrics)
{ {
return; return;
@@ -441,7 +453,7 @@ namespace Unity.Netcode
public void TrackPacketReceived(uint packetCount) public void TrackPacketReceived(uint packetCount)
{ {
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
if (!CanSendMetrics) if (!CanSendMetrics)
{ {
return; return;
@@ -452,15 +464,51 @@ namespace Unity.Netcode
#endif #endif
} }
public void TrackRttToServer(int rtt) public void UpdateRttToServer(int rttMilliseconds)
{ {
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
if (!CanSendMetrics)
{
return;
}
var rttSeconds = rttMilliseconds * 1e-3;
m_RttToServerGauge.Set(rttSeconds);
#endif
}
public void UpdateNetworkObjectsCount(int count)
{
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
if (!CanSendMetrics) if (!CanSendMetrics)
{ {
return; return;
} }
m_RttToServerGauge.Set(rtt); m_NetworkObjectsGauge.Set(count);
#endif
}
public void UpdateConnectionsCount(int count)
{
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
if (!CanSendMetrics)
{
return;
}
m_ConnectionsGauge.Set(count);
#endif
}
public void UpdatePacketLoss(float packetLoss)
{
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
if (!CanSendMetrics)
{
return;
}
m_PacketLossGauge.Set(packetLoss);
#endif #endif
} }

View File

@@ -145,7 +145,19 @@ namespace Unity.Netcode
{ {
} }
public void TrackRttToServer(int rtt) public void UpdateRttToServer(int rtt)
{
}
public void UpdateNetworkObjectsCount(int count)
{
}
public void UpdateConnectionsCount(int count)
{
}
public void UpdatePacketLoss(float packetLoss)
{ {
} }

View File

@@ -8,9 +8,10 @@ namespace Unity.Netcode
/// Event based NetworkVariable container for syncing Lists /// Event based NetworkVariable container for syncing Lists
/// </summary> /// </summary>
/// <typeparam name="T">The type for the list</typeparam> /// <typeparam name="T">The type for the list</typeparam>
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T> public class NetworkList<T> : NetworkVariableSerialization<T> 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>
@@ -24,17 +25,12 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public event OnListChangedDelegate OnListChanged; public event OnListChangedDelegate OnListChanged;
/// <summary>
/// Creates a NetworkList with the default value and settings
/// </summary>
public NetworkList() { } public NetworkList() { }
/// <summary> public NetworkList(IEnumerable<T> values = default,
/// Creates a NetworkList with the default value and custom settings NetworkVariableReadPermission readPerm = DefaultReadPerm,
/// </summary> NetworkVariableWritePermission writePerm = DefaultWritePerm)
/// <param name="readPerm">The read permission to use for the NetworkList</param> : base(readPerm, writePerm)
/// <param name="values">The initial value to use for the NetworkList</param>
public NetworkList(NetworkVariableReadPermission readPerm, IEnumerable<T> values) : base(readPerm)
{ {
foreach (var value in values) foreach (var value in values)
{ {
@@ -42,24 +38,15 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Creates a NetworkList with a custom value and the default settings
/// </summary>
/// <param name="values">The initial value to use for the NetworkList</param>
public NetworkList(IEnumerable<T> values)
{
foreach (var value in values)
{
m_List.Add(value);
}
}
/// <inheritdoc /> /// <inheritdoc />
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 />
@@ -90,18 +77,18 @@ namespace Unity.Netcode
{ {
case NetworkListEvent<T>.EventType.Add: case NetworkListEvent<T>.EventType.Add:
{ {
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value); Write(writer, m_DirtyEvents[i].Value);
} }
break; break;
case NetworkListEvent<T>.EventType.Insert: case NetworkListEvent<T>.EventType.Insert:
{ {
writer.WriteValueSafe(m_DirtyEvents[i].Index); writer.WriteValueSafe(m_DirtyEvents[i].Index);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value); Write(writer, m_DirtyEvents[i].Value);
} }
break; break;
case NetworkListEvent<T>.EventType.Remove: case NetworkListEvent<T>.EventType.Remove:
{ {
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value); Write(writer, m_DirtyEvents[i].Value);
} }
break; break;
case NetworkListEvent<T>.EventType.RemoveAt: case NetworkListEvent<T>.EventType.RemoveAt:
@@ -112,7 +99,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Value: case NetworkListEvent<T>.EventType.Value:
{ {
writer.WriteValueSafe(m_DirtyEvents[i].Index); writer.WriteValueSafe(m_DirtyEvents[i].Index);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value); Write(writer, m_DirtyEvents[i].Value);
} }
break; break;
case NetworkListEvent<T>.EventType.Clear: case NetworkListEvent<T>.EventType.Clear:
@@ -127,10 +114,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]); Write(writer, m_ListAtLastReset[i]);
} }
} }
@@ -141,7 +128,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); Read(reader, out T value);
m_List.Add(value); m_List.Add(value);
} }
} }
@@ -157,7 +144,7 @@ namespace Unity.Netcode
{ {
case NetworkListEvent<T>.EventType.Add: case NetworkListEvent<T>.EventType.Add:
{ {
NetworkVariable<T>.Read(reader, out T value); Read(reader, out T value);
m_List.Add(value); m_List.Add(value);
if (OnListChanged != null) if (OnListChanged != null)
@@ -184,7 +171,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); 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;
@@ -211,7 +198,7 @@ namespace Unity.Netcode
break; break;
case NetworkListEvent<T>.EventType.Remove: case NetworkListEvent<T>.EventType.Remove:
{ {
NetworkVariable<T>.Read(reader, out T value); Read(reader, out T value);
int index = m_List.IndexOf(value); int index = m_List.IndexOf(value);
if (index == -1) if (index == -1)
{ {
@@ -271,7 +258,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); 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");
@@ -472,6 +459,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();
} }
} }

View File

@@ -1,5 +1,7 @@
using UnityEngine; using UnityEngine;
using System; using System;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -7,57 +9,8 @@ namespace Unity.Netcode
/// A variable that can be synchronized over the network. /// A variable that can be synchronized over the network.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged public class NetworkVariable<T> : NetworkVariableSerialization<T> 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>
@@ -69,38 +22,11 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public OnValueChangedDelegate OnValueChanged; public OnValueChangedDelegate OnValueChanged;
/// <summary>
/// Creates a NetworkVariable with the default value and custom read permission
/// </summary>
/// <param name="readPerm">The read permission for the NetworkVariable</param>
public NetworkVariable() public NetworkVariable(T value = default,
{ NetworkVariableReadPermission readPerm = DefaultReadPerm,
} NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
/// <summary>
/// Creates a NetworkVariable with the default value and custom read permission
/// </summary>
/// <param name="readPerm">The read permission for the NetworkVariable</param>
public NetworkVariable(NetworkVariableReadPermission readPerm) : base(readPerm)
{
}
/// <summary>
/// Creates a NetworkVariable with a custom value and custom settings
/// </summary>
/// <param name="readPerm">The read permission for the NetworkVariable</param>
/// <param name="value">The initial value to use for the NetworkVariable</param>
public NetworkVariable(NetworkVariableReadPermission readPerm, T value) : base(readPerm)
{
m_InternalValue = value;
}
/// <summary>
/// Creates a NetworkVariable with a custom value and the default read permission
/// </summary>
/// <param name="value">The initial value to use for the NetworkVariable</param>
public NetworkVariable(T value)
{ {
m_InternalValue = value; m_InternalValue = value;
} }
@@ -116,19 +42,36 @@ namespace Unity.Netcode
get => m_InternalValue; get => m_InternalValue;
set set
{ {
// this could be improved. The Networking Manager is not always initialized here // Compare bitwise
// Good place to decouple network manager from the network variable if (ValueEquals(ref m_InternalValue, ref value))
// Also, note this is not really very water-tight, if you are running as a host
// we cannot tell if a NetworkVariable write is happening inside client-ish code
if (m_NetworkBehaviour && (m_NetworkBehaviour.NetworkManager.IsClient && !m_NetworkBehaviour.NetworkManager.IsHost))
{ {
throw new InvalidOperationException("Client can't write to NetworkVariables"); return;
} }
if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable");
}
Set(value); Set(value);
} }
} }
// Compares two values of the same unmanaged type by underlying memory
// Ignoring any overriden value checks
// Size is fixed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe bool ValueEquals(ref T a, ref T b)
{
// get unmanaged pointers
var aptr = UnsafeUtility.AddressOf(ref a);
var bptr = UnsafeUtility.AddressOf(ref b);
// compare addresses
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
}
private protected void Set(T value) private protected void Set(T value)
{ {
m_IsDirty = true; m_IsDirty = true;
@@ -146,7 +89,6 @@ namespace Unity.Netcode
WriteField(writer); WriteField(writer);
} }
/// <summary> /// <summary>
/// Reads value from the reader and applies it /// Reads value from the reader and applies it
/// </summary> /// </summary>
@@ -154,6 +96,11 @@ 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); Read(reader, out m_InternalValue);

View File

@@ -19,9 +19,15 @@ namespace Unity.Netcode
m_NetworkBehaviour = networkBehaviour; m_NetworkBehaviour = networkBehaviour;
} }
protected NetworkVariableBase(NetworkVariableReadPermission readPermIn = NetworkVariableReadPermission.Everyone) public const NetworkVariableReadPermission DefaultReadPerm = NetworkVariableReadPermission.Everyone;
public const NetworkVariableWritePermission DefaultWritePerm = NetworkVariableWritePermission.Server;
protected NetworkVariableBase(
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
{ {
ReadPerm = readPermIn; ReadPerm = readPerm;
WritePerm = writePerm;
} }
private protected bool m_IsDirty; private protected bool m_IsDirty;
@@ -37,6 +43,8 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public readonly NetworkVariableReadPermission ReadPerm; public readonly NetworkVariableReadPermission ReadPerm;
public readonly NetworkVariableWritePermission WritePerm;
/// <summary> /// <summary>
/// Sets whether or not the variable needs to be delta synced /// Sets whether or not the variable needs to be delta synced
/// </summary> /// </summary>
@@ -62,26 +70,36 @@ namespace Unity.Netcode
return m_IsDirty; return m_IsDirty;
} }
public virtual bool ShouldWrite(ulong clientId, bool isServer)
{
return IsDirty() && isServer && CanClientRead(clientId);
}
/// <summary>
/// Gets Whether or not a specific client can read to the varaible
/// </summary>
/// <param name="clientId">The clientId of the remote client</param>
/// <returns>Whether or not the client can read to the variable</returns>
public bool CanClientRead(ulong clientId) public bool CanClientRead(ulong clientId)
{ {
switch (ReadPerm) switch (ReadPerm)
{ {
default:
case NetworkVariableReadPermission.Everyone: case NetworkVariableReadPermission.Everyone:
return true; return true;
case NetworkVariableReadPermission.OwnerOnly: case NetworkVariableReadPermission.Owner:
return m_NetworkBehaviour.OwnerClientId == clientId; return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
} }
return true; }
public bool CanClientWrite(ulong clientId)
{
switch (WritePerm)
{
default:
case NetworkVariableWritePermission.Server:
return clientId == NetworkManager.ServerClientId;
case NetworkVariableWritePermission.Owner:
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
}
}
/// <summary>
/// Returns the ClientId of the owning client
/// </summary>
internal ulong OwnerClientId()
{
return m_NetworkBehaviour.NetworkObject.OwnerClientId;
} }
/// <summary> /// <summary>
@@ -107,7 +125,6 @@ namespace Unity.Netcode
/// </summary> /// </summary>
/// <param name="reader">The stream to read the delta from</param> /// <param name="reader">The stream to read the delta from</param>
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param> /// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta); public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
public virtual void Dispose() public virtual void Dispose()

View File

@@ -1,3 +1,6 @@
using System;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
public class NetworkVariableHelper public class NetworkVariableHelper
@@ -13,10 +16,62 @@ namespace Unity.Netcode
// side, but it gets the best achievable user experience and performance. // side, but it gets the best achievable user experience and performance.
// //
// RuntimeAccessModifiersILPP will make this `public` // RuntimeAccessModifiersILPP will make this `public`
internal static void InitializeDelegates<T>() where T : unmanaged, INetworkSerializable internal static void InitializeDelegatesNetworkSerializable<T>() where T : unmanaged, INetworkSerializable
{ {
NetworkVariable<T>.Write = NetworkVariable<T>.WriteNetworkSerializable; NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteNetworkSerializable);
NetworkVariable<T>.Read = NetworkVariable<T>.ReadNetworkSerializable; NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadNetworkSerializable);
}
internal static void InitializeDelegatesStruct<T>() where T : unmanaged, INetworkSerializeByMemcpy
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteStruct);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadStruct);
}
internal static void InitializeDelegatesEnum<T>() where T : unmanaged, Enum
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteEnum);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadEnum);
}
internal static void InitializeDelegatesPrimitive<T>() where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WritePrimitive);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadPrimitive);
}
internal static void InitializeAllBaseDelegates()
{
// Built-in C# types, serialized through a generic method
InitializeDelegatesPrimitive<bool>();
InitializeDelegatesPrimitive<byte>();
InitializeDelegatesPrimitive<sbyte>();
InitializeDelegatesPrimitive<char>();
InitializeDelegatesPrimitive<decimal>();
InitializeDelegatesPrimitive<float>();
InitializeDelegatesPrimitive<double>();
InitializeDelegatesPrimitive<short>();
InitializeDelegatesPrimitive<ushort>();
InitializeDelegatesPrimitive<int>();
InitializeDelegatesPrimitive<uint>();
InitializeDelegatesPrimitive<long>();
InitializeDelegatesPrimitive<ulong>();
// Built-in Unity types, serialized with specific overloads because they're structs without ISerializeByMemcpy attached
NetworkVariableSerialization<Vector2>.SetWriteDelegate((FastBufferWriter writer, in Vector2 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector3>.SetWriteDelegate((FastBufferWriter writer, in Vector3 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector4>.SetWriteDelegate((FastBufferWriter writer, in Vector4 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Quaternion>.SetWriteDelegate((FastBufferWriter writer, in Quaternion value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Color>.SetWriteDelegate((FastBufferWriter writer, in Color value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Color32>.SetWriteDelegate((FastBufferWriter writer, in Color32 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Ray>.SetWriteDelegate((FastBufferWriter writer, in Ray value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Ray2D>.SetWriteDelegate((FastBufferWriter writer, in Ray2D value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector2>.SetReadDelegate((FastBufferReader reader, out Vector2 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Vector3>.SetReadDelegate((FastBufferReader reader, out Vector3 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Vector4>.SetReadDelegate((FastBufferReader reader, out Vector4 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Quaternion>.SetReadDelegate((FastBufferReader reader, out Quaternion value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Color>.SetReadDelegate((FastBufferReader reader, out Color value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Color32>.SetReadDelegate((FastBufferReader reader, out Color32 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Ray>.SetReadDelegate((FastBufferReader reader, out Ray value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Ray2D>.SetReadDelegate((FastBufferReader reader, out Ray2D value) => { reader.ReadValueSafe(out value); });
} }
} }
} }

View File

@@ -1,18 +1,14 @@
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Permission type
/// </summary>
public enum NetworkVariableReadPermission public enum NetworkVariableReadPermission
{ {
/// <summary>
/// Everyone
/// </summary>
Everyone, Everyone,
Owner,
}
/// <summary> public enum NetworkVariableWritePermission
/// Owner-ownly {
/// </summary> Server,
OwnerOnly, Owner
} }
} }

View File

@@ -0,0 +1,169 @@
using System;
namespace Unity.Netcode
{
/// <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 includes various read/write delegates
/// based on which constraints are met by `T`. These constraints are set up by `NetworkVariableHelpers`,
/// which is invoked by code generated by ILPP during module load.
/// (As it turns out, IL has support for a module initializer that C# doesn't expose.)
/// This installs the correct delegate for each `T` to ensure that each type is serialized properly.
///
/// Any type that inherits from `NetworkVariableSerialization<T>` will implicitly result in any `T`
/// passed to it being picked up and initialized by ILPP.
///
/// The methods here, despite being static, are `protected` specifically to ensure that anything that
/// wants access to them has to inherit from this base class, thus enabling ILPP to find and initialize it.
/// </summary>
[Serializable]
public abstract class NetworkVariableSerialization<T> : NetworkVariableBase where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
{
writer.WriteNetworkSerializable(value);
}
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize structs
internal static void WriteStruct<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
writer.WriteValueSafe(value);
}
internal static void ReadStruct<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
reader.ReadValueSafe(out value);
}
// Functions that serialize enums
internal static void WriteEnum<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, Enum
{
writer.WriteValueSafe(value);
}
internal static void ReadEnum<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, Enum
{
reader.ReadValueSafe(out value);
}
// Functions that serialize other types
internal static void WritePrimitive<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
writer.WriteValueSafe(value);
}
internal static void ReadPrimitive<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
reader.ReadValueSafe(out value);
}
// Should never be reachable at runtime. All calls to this should be replaced with the correct
// call above by ILPP.
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged
{
if (value is INetworkSerializable)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (value is INetworkSerializeByMemcpy)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (value is Enum)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
}
NetworkVariableSerialization<TForMethod>.Write(writer, value);
}
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
{
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(Enum).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
}
NetworkVariableSerialization<TForMethod>.Read(reader, out value);
}
protected internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
protected 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.
private static WriteDelegate<T> s_Write = WriteValue;
private static ReadDelegate<T> s_Read = ReadValue;
protected static void Write(FastBufferWriter writer, in T value)
{
s_Write(writer, value);
}
protected static void Read(FastBufferReader reader, out T value)
{
s_Read(reader, out value);
}
internal static void SetWriteDelegate(WriteDelegate<T> write)
{
s_Write = write;
}
internal static void SetReadDelegate(ReadDelegate<T> read)
{
s_Read = read;
}
protected NetworkVariableSerialization(
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2c6ef5fdf2e94ec3b4ce8086d52700b3
timeCreated: 1650985453

View File

@@ -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;
} }

View File

@@ -132,7 +132,7 @@ namespace Unity.Netcode
private const NetworkDelivery k_DeliveryType = NetworkDelivery.ReliableFragmentedSequenced; private const NetworkDelivery k_DeliveryType = NetworkDelivery.ReliableFragmentedSequenced;
internal const int InvalidSceneNameOrPath = -1; internal const int InvalidSceneNameOrPath = -1;
// Used to be able to turn re-synchronization off for future snapshot development purposes. // Used to be able to turn re-synchronization off
internal static bool DisableReSynchronization; internal static bool DisableReSynchronization;
/// <summary> /// <summary>
@@ -488,8 +488,18 @@ namespace Unity.Netcode
var scenePath = SceneUtility.GetScenePathByBuildIndex(i); var scenePath = SceneUtility.GetScenePathByBuildIndex(i);
var hash = XXHash.Hash32(scenePath); var hash = XXHash.Hash32(scenePath);
var buildIndex = SceneUtility.GetBuildIndexByScenePath(scenePath); var buildIndex = SceneUtility.GetBuildIndexByScenePath(scenePath);
HashToBuildIndex.Add(hash, buildIndex);
BuildIndexToHash.Add(buildIndex, hash); // In the rare-case scenario where a programmatically generated build has duplicate
// scene entries, we will log an error and skip the entry
if (!HashToBuildIndex.ContainsKey(hash))
{
HashToBuildIndex.Add(hash, buildIndex);
BuildIndexToHash.Add(buildIndex, hash);
}
else
{
Debug.LogError($"{nameof(NetworkSceneManager)} is skipping duplicate scene path entry {scenePath}. Make sure your scenes in build list does not contain duplicates!");
}
} }
} }
@@ -520,7 +530,8 @@ namespace Unity.Netcode
} }
else else
{ {
throw new Exception($"Scene Hash {sceneHash} does not exist in the {nameof(HashToBuildIndex)} table!"); throw new Exception($"Scene Hash {sceneHash} does not exist in the {nameof(HashToBuildIndex)} table! Verify that all scenes requiring" +
$" server to client synchronization are in the scenes in build list.");
} }
} }
@@ -876,7 +887,7 @@ namespace Unity.Netcode
{ {
SceneEventType = sceneEventProgress.SceneEventType, SceneEventType = sceneEventProgress.SceneEventType,
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash), SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
ClientId = m_NetworkManager.ServerClientId, ClientId = NetworkManager.ServerClientId,
LoadSceneMode = sceneEventProgress.LoadSceneMode, LoadSceneMode = sceneEventProgress.LoadSceneMode,
ClientsThatCompleted = sceneEventProgress.DoneClients, ClientsThatCompleted = sceneEventProgress.DoneClients,
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(), ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(),
@@ -947,10 +958,10 @@ namespace Unity.Netcode
SceneEventType = sceneEventData.SceneEventType, SceneEventType = sceneEventData.SceneEventType,
LoadSceneMode = sceneEventData.LoadSceneMode, LoadSceneMode = sceneEventData.LoadSceneMode,
SceneName = sceneName, SceneName = sceneName,
ClientId = m_NetworkManager.ServerClientId // Server can only invoke this ClientId = NetworkManager.ServerClientId // Server can only invoke this
}); });
OnUnload?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneUnload); OnUnload?.Invoke(NetworkManager.ServerClientId, sceneName, sceneUnload);
//Return the status //Return the status
return sceneEventProgress.Status; return sceneEventProgress.Status;
@@ -1017,12 +1028,12 @@ namespace Unity.Netcode
// Server sends the unload scene notification after unloading because it will despawn all scene relative in-scene NetworkObjects // Server sends the unload scene notification after unloading because it will despawn all scene relative in-scene NetworkObjects
// If we send this event to all clients before the server is finished unloading they will get warning about an object being // If we send this event to all clients before the server is finished unloading they will get warning about an object being
// despawned that no longer exists // despawned that no longer exists
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray()); SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray());
//Only if we are a host do we want register having loaded for the associated SceneEventProgress //Only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
} }
} }
@@ -1035,7 +1046,7 @@ namespace Unity.Netcode
SceneEventType = sceneEventData.SceneEventType, SceneEventType = sceneEventData.SceneEventType,
LoadSceneMode = sceneEventData.LoadSceneMode, LoadSceneMode = sceneEventData.LoadSceneMode,
SceneName = SceneNameFromHash(sceneEventData.SceneHash), SceneName = SceneNameFromHash(sceneEventData.SceneHash),
ClientId = m_NetworkManager.IsServer ? m_NetworkManager.ServerClientId : m_NetworkManager.LocalClientId ClientId = m_NetworkManager.IsServer ? NetworkManager.ServerClientId : m_NetworkManager.LocalClientId
}); });
OnUnloadComplete?.Invoke(m_NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash)); OnUnloadComplete?.Invoke(m_NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash));
@@ -1043,7 +1054,7 @@ namespace Unity.Netcode
// Clients send a notification back to the server they have completed the unload scene event // Clients send a notification back to the server they have completed the unload scene event
if (!m_NetworkManager.IsServer) if (!m_NetworkManager.IsServer)
{ {
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId }); SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
} }
EndSceneEvent(sceneEventId); EndSceneEvent(sceneEventId);
@@ -1079,7 +1090,7 @@ namespace Unity.Netcode
SceneEventType = SceneEventType.Unload, SceneEventType = SceneEventType.Unload,
SceneName = keyHandleEntry.Value.name, SceneName = keyHandleEntry.Value.name,
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
ClientId = m_NetworkManager.ServerClientId ClientId = NetworkManager.ServerClientId
}); });
} }
} }
@@ -1147,10 +1158,10 @@ namespace Unity.Netcode
SceneEventType = sceneEventData.SceneEventType, SceneEventType = sceneEventData.SceneEventType,
LoadSceneMode = sceneEventData.LoadSceneMode, LoadSceneMode = sceneEventData.LoadSceneMode,
SceneName = sceneName, SceneName = sceneName,
ClientId = m_NetworkManager.ServerClientId ClientId = NetworkManager.ServerClientId
}); });
OnLoad?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
//Return our scene progress instance //Return our scene progress instance
return sceneEventProgress.Status; return sceneEventProgress.Status;
@@ -1187,7 +1198,6 @@ namespace Unity.Netcode
// When it is set: Just before starting the asynchronous loading call // When it is set: Just before starting the asynchronous loading call
// When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do // When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do
// not destroy temporary scene are moved into the active scene // not destroy temporary scene are moved into the active scene
// TODO: When Snapshot scene spawning is enabled this needs to be removed.
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single) if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
{ {
IsSpawnedObjectsPendingInDontDestroyOnLoad = true; IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
@@ -1278,7 +1288,9 @@ namespace Unity.Netcode
{ {
if (!keyValuePairBySceneHandle.Value.IsPlayerObject) if (!keyValuePairBySceneHandle.Value.IsPlayerObject)
{ {
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, null, true); // All in-scene placed NetworkObjects default to being owned by the server
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value,
m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, NetworkManager.ServerClientId, true);
} }
} }
} }
@@ -1290,7 +1302,7 @@ namespace Unity.Netcode
for (int j = 0; j < m_NetworkManager.ConnectedClientsList.Count; j++) for (int j = 0; j < m_NetworkManager.ConnectedClientsList.Count; j++)
{ {
var clientId = m_NetworkManager.ConnectedClientsList[j].ClientId; var clientId = m_NetworkManager.ConnectedClientsList[j].ClientId;
if (clientId != m_NetworkManager.ServerClientId) if (clientId != NetworkManager.ServerClientId)
{ {
sceneEventData.TargetClientId = clientId; sceneEventData.TargetClientId = clientId;
var message = new SceneEventMessage var message = new SceneEventMessage
@@ -1309,16 +1321,16 @@ namespace Unity.Netcode
SceneEventType = SceneEventType.LoadComplete, SceneEventType = SceneEventType.LoadComplete,
LoadSceneMode = sceneEventData.LoadSceneMode, LoadSceneMode = sceneEventData.LoadSceneMode,
SceneName = SceneNameFromHash(sceneEventData.SceneHash), SceneName = SceneNameFromHash(sceneEventData.SceneHash),
ClientId = m_NetworkManager.ServerClientId, ClientId = NetworkManager.ServerClientId,
Scene = scene, Scene = scene,
}); });
OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); OnLoadComplete?.Invoke(NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
} }
EndSceneEvent(sceneEventId); EndSceneEvent(sceneEventId);
} }
@@ -1333,7 +1345,7 @@ namespace Unity.Netcode
sceneEventData.DeserializeScenePlacedObjects(); sceneEventData.DeserializeScenePlacedObjects();
sceneEventData.SceneEventType = SceneEventType.LoadComplete; sceneEventData.SceneEventType = SceneEventType.LoadComplete;
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId }); SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
m_IsSceneEventActive = false; m_IsSceneEventActive = false;
// Notify local client that the scene was loaded // Notify local client that the scene was loaded
@@ -1434,12 +1446,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.ClientSceneHandle = 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)
@@ -1456,9 +1465,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;
@@ -1544,9 +1560,9 @@ namespace Unity.Netcode
{ {
EventData = responseSceneEventData EventData = responseSceneEventData
}; };
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ServerClientId); var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(m_NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size); m_NetworkManager.NetworkMetrics.TrackSceneEventSent(NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size);
EndSceneEvent(responseSceneEventData.SceneEventId); EndSceneEvent(responseSceneEventData.SceneEventId);
@@ -1600,7 +1616,7 @@ namespace Unity.Netcode
sceneEventData.SynchronizeSceneNetworkObjects(m_NetworkManager); sceneEventData.SynchronizeSceneNetworkObjects(m_NetworkManager);
sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete; sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete;
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId }); SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
// All scenes are synchronized, let the server know we are done synchronizing // All scenes are synchronized, let the server know we are done synchronizing
m_NetworkManager.IsConnectedClient = true; m_NetworkManager.IsConnectedClient = true;
@@ -1627,7 +1643,7 @@ namespace Unity.Netcode
OnSceneEvent?.Invoke(new SceneEvent() OnSceneEvent?.Invoke(new SceneEvent()
{ {
SceneEventType = sceneEventData.SceneEventType, SceneEventType = sceneEventData.SceneEventType,
ClientId = m_NetworkManager.ServerClientId, // Server sent this to client ClientId = NetworkManager.ServerClientId, // Server sent this to client
}); });
EndSceneEvent(sceneEventId); EndSceneEvent(sceneEventId);
@@ -1642,7 +1658,7 @@ namespace Unity.Netcode
SceneEventType = sceneEventData.SceneEventType, SceneEventType = sceneEventData.SceneEventType,
LoadSceneMode = sceneEventData.LoadSceneMode, LoadSceneMode = sceneEventData.LoadSceneMode,
SceneName = SceneNameFromHash(sceneEventData.SceneHash), SceneName = SceneNameFromHash(sceneEventData.SceneHash),
ClientId = m_NetworkManager.ServerClientId, ClientId = NetworkManager.ServerClientId,
ClientsThatCompleted = sceneEventData.ClientsCompleted, ClientsThatCompleted = sceneEventData.ClientsCompleted,
ClientsThatTimedOut = sceneEventData.ClientsTimedOut, ClientsThatTimedOut = sceneEventData.ClientsTimedOut,
}); });
@@ -1734,9 +1750,9 @@ namespace Unity.Netcode
// NetworkObjects // NetworkObjects
m_NetworkManager.InvokeOnClientConnectedCallback(clientId); m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
// TODO: This check and associated code can be removed once we determine all // Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid
// snapshot destroy messages are being updated until the server receives ACKs // a potential crash within the MessageSystem (i.e. sending to a client that no longer exists)
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization) 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 });
@@ -1830,7 +1846,7 @@ namespace Unity.Netcode
/// Using the local scene relative Scene.handle as a sub-key to the root dictionary allows us to /// Using the local scene relative Scene.handle as a sub-key to the root dictionary allows us to
/// distinguish between duplicate in-scene placed NetworkObjects /// distinguish between duplicate in-scene placed NetworkObjects
/// </summary> /// </summary>
private void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true) internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true)
{ {
if (clearScenePlacedObjects) if (clearScenePlacedObjects)
{ {
@@ -1845,25 +1861,26 @@ namespace Unity.Netcode
// at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects // at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects
foreach (var networkObjectInstance in networkObjects) foreach (var networkObjectInstance in networkObjects)
{ {
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (additive scenes) var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
if (networkObjectInstance.IsSceneObject == null && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy && var sceneHandle = networkObjectInstance.gameObject.scene.handle;
networkObjectInstance.gameObject.scene.handle == sceneToFilterBy.handle) // 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 &&
sceneHandle == sceneToFilterBy.handle)
{ {
if (!ScenePlacedObjects.ContainsKey(networkObjectInstance.GlobalObjectIdHash)) if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{ {
ScenePlacedObjects.Add(networkObjectInstance.GlobalObjectIdHash, new Dictionary<int, NetworkObject>()); ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
} }
if (!ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].ContainsKey(networkObjectInstance.gameObject.scene.handle)) if (!ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
{ {
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].Add(networkObjectInstance.gameObject.scene.handle, networkObjectInstance); ScenePlacedObjects[globalObjectIdHash].Add(sceneHandle, networkObjectInstance);
} }
else else
{ {
var exitingEntryName = ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle] != null ? var exitingEntryName = ScenePlacedObjects[globalObjectIdHash][sceneHandle] != null ? ScenePlacedObjects[globalObjectIdHash][sceneHandle].name : "Null Entry";
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle].name : "Null Entry";
throw new Exception($"{networkObjectInstance.name} tried to registered with {nameof(ScenePlacedObjects)} which already contains " + throw new Exception($"{networkObjectInstance.name} tried to registered with {nameof(ScenePlacedObjects)} which already contains " +
$"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {networkObjectInstance.GlobalObjectIdHash} for {exitingEntryName}!"); $"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {globalObjectIdHash} for {exitingEntryName}!");
} }
} }
} }

View File

@@ -41,7 +41,6 @@ namespace Unity.Netcode
/// <b>Invocation:</b> Server Side<br/> /// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/> /// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification<br/> /// <b>Event Notification:</b> Both server and client receive a local notification<br/>
/// <em>Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point.</em>
/// </summary> /// </summary>
ReSynchronize, ReSynchronize,
/// <summary> /// <summary>
@@ -92,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;
@@ -592,9 +591,6 @@ namespace Unity.Netcode
networkObject.IsSpawned = false; networkObject.IsSpawned = false;
if (m_NetworkManager.PrefabHandler.ContainsHandler(networkObject)) if (m_NetworkManager.PrefabHandler.ContainsHandler(networkObject))
{ {
// Since this is the client side and we have missed the delete message, until the Snapshot system is in place for spawn and despawn handling
// we have to remove this from the list of spawned objects manually or when a NetworkObjectId is recycled the client will throw an error
// about the id already being assigned.
if (m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) if (m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
{ {
m_NetworkManager.SpawnManager.SpawnedObjects.Remove(networkObjectId); m_NetworkManager.SpawnManager.SpawnedObjects.Remove(networkObjectId);

View File

@@ -1,13 +1,16 @@
using System;
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 +61,63 @@ 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 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> public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value);
/// Serialize a string.
///
/// Note: Will ALWAYS allocate a new string when reading.
///
/// 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="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>
/// 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&lt;type&gt;()
/// </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 Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value);
/// </param> public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value);
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value);
{ public void SerializeValuePreChecked(ref Quaternion[] value) => m_Implementation.SerializeValuePreChecked(ref value);
m_Implementation.SerializeValuePreChecked(ref s, oneByteChars); 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);
/// <summary> public void SerializeValuePreChecked(ref Color32[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// Serialize an array value. public void SerializeValuePreChecked(ref Ray value) => m_Implementation.SerializeValuePreChecked(ref value);
/// public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// Note: Will ALWAYS allocate a new array when reading. public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value);
/// If you have a statically-sized array that you know is large enough, it's recommended to public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// 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.)
///
/// 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);
}
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -24,54 +25,63 @@ 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(ref Vector2 value) => m_Reader.ReadValueSafe(out value);
public void SerializeValue(ref Vector2[] value) => m_Reader.ReadValueSafe(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 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[] array) where T : unmanaged public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializable(out value);
{
m_Reader.ReadValueSafe(out array);
}
public void SerializeValue(ref byte value)
{
m_Reader.ReadByteSafe(out value);
}
public void SerializeValue<T>(ref T value) where T : unmanaged
{
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(ref Vector2 value) => m_Reader.ReadValue(out value);
public void SerializeValuePreChecked(ref Vector2[] value) => m_Reader.ReadValue(out value);
public void SerializeValuePreChecked(ref byte value) public void SerializeValuePreChecked(ref Vector3 value) => m_Reader.ReadValue(out value);
{ public void SerializeValuePreChecked(ref Vector3[] value) => m_Reader.ReadValue(out 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<T>(ref T value) where T : unmanaged public void SerializeValuePreChecked(ref Quaternion[] value) => m_Reader.ReadValue(out value);
{ public void SerializeValuePreChecked(ref Color value) => m_Reader.ReadValue(out 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);
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -24,25 +25,32 @@ 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[] array) where T : unmanaged 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);
m_Writer.WriteValueSafe(array); 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(ref byte value) public void SerializeValue(ref Vector2 value) => m_Writer.WriteValueSafe(value);
{ public void SerializeValue(ref Vector2[] value) => m_Writer.WriteValueSafe(value);
m_Writer.WriteByteSafe(value); public void SerializeValue(ref Vector3 value) => m_Writer.WriteValueSafe(value);
} public void SerializeValue(ref Vector3[] 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 Vector4[] value) => m_Writer.WriteValueSafe(value);
{ public void SerializeValue(ref Quaternion value) => m_Writer.WriteValueSafe(value);
m_Writer.WriteValueSafe(value); public void SerializeValue(ref Quaternion[] 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 +62,30 @@ 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(ref Vector2 value) => m_Writer.WriteValue(value);
public void SerializeValuePreChecked(ref byte value) public void SerializeValuePreChecked(ref Vector2[] value) => m_Writer.WriteValue(value);
{ public void SerializeValuePreChecked(ref Vector3 value) => m_Writer.WriteValue(value);
m_Writer.WriteByte(value); public void SerializeValuePreChecked(ref Vector3[] 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<T>(ref T value) where T : unmanaged public void SerializeValuePreChecked(ref Quaternion value) => m_Writer.WriteValue(value);
{ public void SerializeValuePreChecked(ref Quaternion[] value) => m_Writer.WriteValue(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);
} }
} }

View File

@@ -10,7 +10,7 @@ namespace Unity.Netcode
public static class BytePacker public static class BytePacker
{ {
#if UNITY_NETCODE_DEBUG_NO_PACKING #if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValuePacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value); public void WriteValuePacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
#else #else
@@ -277,10 +277,21 @@ namespace Unity.Netcode
#if UNITY_NETCODE_DEBUG_NO_PACKING #if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value); public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
#else #else
public const ushort BitPackedUshortMax = (1 << 15) - 1;
public const short BitPackedShortMax = (1 << 14) - 1;
public const short BitPackedShortMin = -(1 << 14);
public const uint BitPackedUintMax = (1 << 30) - 1;
public const int BitPackedIntMax = (1 << 29) - 1;
public const int BitPackedIntMin = -(1 << 29);
public const ulong BitPackedULongMax = (1L << 61) - 1;
public const long BitPackedLongMax = (1L << 60) - 1;
public const long BitPackedLongMin = -(1L << 60);
/// <summary> /// <summary>
/// Writes a 14-bit signed short to the buffer in a bit-encoded packed format. /// Writes a 14-bit signed short to the buffer in a bit-encoded packed format.
/// The first bit indicates whether the value is 1 byte or 2. /// The first bit indicates whether the value is 1 byte or 2.
@@ -307,7 +318,7 @@ namespace Unity.Netcode
public static void WriteValueBitPacked(FastBufferWriter writer, ushort value) public static void WriteValueBitPacked(FastBufferWriter writer, ushort value)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value >= 0b1000_0000_0000_0000) if (value >= BitPackedUshortMax)
{ {
throw new ArgumentException("BitPacked ushorts must be <= 15 bits"); throw new ArgumentException("BitPacked ushorts must be <= 15 bits");
} }
@@ -356,7 +367,7 @@ namespace Unity.Netcode
public static void WriteValueBitPacked(FastBufferWriter writer, uint value) public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value >= 0b0100_0000_0000_0000_0000_0000_0000_0000) if (value > BitPackedUintMax)
{ {
throw new ArgumentException("BitPacked uints must be <= 30 bits"); throw new ArgumentException("BitPacked uints must be <= 30 bits");
} }
@@ -396,7 +407,7 @@ namespace Unity.Netcode
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value) public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value >= 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000) if (value > BitPackedULongMax)
{ {
throw new ArgumentException("BitPacked ulongs must be <= 61 bits"); throw new ArgumentException("BitPacked ulongs must be <= 61 bits");
} }

View File

@@ -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
{ {
@@ -19,7 +20,7 @@ namespace Unity.Netcode
#endif #endif
} }
internal readonly unsafe ReaderHandle* Handle; internal unsafe ReaderHandle* Handle;
/// <summary> /// <summary>
/// Get the current read position /// Get the current read position
@@ -39,6 +40,11 @@ namespace Unity.Netcode
get => Handle->Length; get => Handle->Length;
} }
/// <summary>
/// Gets a value indicating whether the reader has been initialized and a handle allocated.
/// </summary>
public unsafe bool IsInitialized => Handle != null;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void CommitBitwiseReads(int amount) internal unsafe void CommitBitwiseReads(int amount)
{ {
@@ -190,12 +196,37 @@ namespace Unity.Netcode
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator); Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator);
} }
/// <summary>
/// Create a FastBufferReader from another existing FastBufferReader. This is typically used when you
/// want to change the allocator 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 allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
/// The exception to this is when the allocator 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).
/// </summary>
/// <param name="reader">The reader to copy from</param>
/// <param name="allocator">The allocator to use</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>
public unsafe FastBufferReader(FastBufferReader reader, Allocator allocator, int length = -1, int offset = 0)
{
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, allocator);
}
/// <summary> /// <summary>
/// Frees the allocated buffer /// Frees the allocated buffer
/// </summary> /// </summary>
public unsafe void Dispose() public unsafe void Dispose()
{ {
UnsafeUtility.Free(Handle, Handle->Allocator); UnsafeUtility.Free(Handle, Handle->Allocator);
Handle = null;
} }
/// <summary> /// <summary>
@@ -486,61 +517,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>
@@ -705,69 +681,155 @@ 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 private 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));
} }
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 private unsafe void ReadUnmanagedSafe<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.");
}
#endif
if (!TryBeginReadInternal(len))
{
throw new OverflowException("Reading past the end of the buffer");
}
fixed (T* ptr = &value) fixed (T* ptr = &value)
{ {
UnsafeUtility.MemCpy((byte*)ptr, Handle->BufferPointer + Handle->Position, len); byte* bytes = (byte*)ptr;
ReadBytesSafe(bytes, sizeof(T));
} }
Handle->Position += len;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private 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)]
private 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);
}
}
[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<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.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(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 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 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);
} }
} }

View File

@@ -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
{ {
@@ -22,7 +23,7 @@ namespace Unity.Netcode
#endif #endif
} }
internal readonly unsafe WriterHandle* Handle; internal unsafe WriterHandle* Handle;
private static byte[] s_ByteArrayCache = new byte[65535]; private static byte[] s_ByteArrayCache = new byte[65535];
@@ -62,6 +63,11 @@ namespace Unity.Netcode
get => Handle->Position > Handle->Length ? Handle->Position : Handle->Length; get => Handle->Position > Handle->Length ? Handle->Position : Handle->Length;
} }
/// <summary>
/// Gets a value indicating whether the writer has been initialized and a handle allocated.
/// </summary>
public unsafe bool IsInitialized => Handle != null;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void CommitBitwiseWrites(int amount) internal unsafe void CommitBitwiseWrites(int amount)
@@ -111,6 +117,7 @@ namespace Unity.Netcode
UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator); UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator);
} }
UnsafeUtility.Free(Handle, Handle->Allocator); UnsafeUtility.Free(Handle, Handle->Allocator);
Handle = null;
} }
/// <summary> /// <summary>
@@ -207,7 +214,7 @@ namespace Unity.Netcode
/// When you know you will be writing multiple fields back-to-back and you know the total size, /// When you know you will be writing multiple fields back-to-back and you know the total size,
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to /// you can call TryBeginWrite() once on the total size, and then follow it with calls to
/// WriteValue() instead of WriteValueSafe() for faster serialization. /// WriteValue() instead of WriteValueSafe() for faster serialization.
/// ///
/// Unsafe write operations will throw OverflowException in editor and development builds if you /// Unsafe write operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
@@ -253,7 +260,7 @@ namespace Unity.Netcode
/// When you know you will be writing multiple fields back-to-back and you know the total size, /// When you know you will be writing multiple fields back-to-back and you know the total size,
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to /// you can call TryBeginWrite() once on the total size, and then follow it with calls to
/// WriteValue() instead of WriteValueSafe() for faster serialization. /// WriteValue() instead of WriteValueSafe() for faster serialization.
/// ///
/// Unsafe write operations will throw OverflowException in editor and development builds if you /// Unsafe write operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
@@ -522,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>
@@ -784,68 +737,170 @@ 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 private 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));
} }
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 private unsafe void WriteUnmanagedSafe<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.");
}
#endif
if (!TryBeginWriteInternal(len))
{
throw new OverflowException("Writing past the end of the buffer");
}
fixed (T* ptr = &value) fixed (T* ptr = &value)
{ {
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len); byte* bytes = (byte*)ptr;
WriteBytesSafe(bytes, sizeof(T));
} }
Handle->Position += len;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteUnmanaged<T>(T[] value) where T : unmanaged
{
WriteUnmanaged(value.Length);
fixed (T* ptr = value)
{
byte* bytes = (byte*)ptr;
WriteBytes(bytes, sizeof(T) * value.Length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private 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);
}
}
// 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
{
}
[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<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, 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(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 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 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);
} }
} }

View 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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d56016695cd44430a345671f7d56b18e
timeCreated: 1647635768

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 11b763f46b18465cbffb1972d737a83e
timeCreated: 1647635592

View File

@@ -1,3 +1,6 @@
using System;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
public interface IReaderWriter public interface IReaderWriter
@@ -9,17 +12,60 @@ 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(ref Vector2 value);
void SerializeValue(ref Vector2[] value);
void SerializeValue(ref Vector3 value);
void SerializeValue(ref Vector3[] 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(ref Vector2 value);
void SerializeValuePreChecked(ref Vector2[] value);
void SerializeValuePreChecked(ref Vector3 value);
void SerializeValuePreChecked(ref Vector3[] 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);
} }
} }

View File

@@ -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
@@ -21,21 +20,120 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public readonly HashSet<NetworkObject> SpawnedObjectsList = new HashSet<NetworkObject>(); public readonly HashSet<NetworkObject> SpawnedObjectsList = new HashSet<NetworkObject>();
private struct TriggerData /// <summary>
/// Use to get all NetworkObjects owned by a client
/// Ownership to Objects Table Format:
/// [ClientId][NetworkObjectId][NetworkObject]
/// Server: Keeps track of all clients' ownership
/// Client: Keeps track of only its ownership
/// </summary>
public readonly Dictionary<ulong, Dictionary<ulong, NetworkObject>> OwnershipToObjectsTable = new Dictionary<ulong, Dictionary<ulong, NetworkObject>>();
/// <summary>
/// Object to Ownership Table:
/// [NetworkObjectId][ClientId]
/// Used internally to find the client Id that currently owns
/// the NetworkObject
/// </summary>
private Dictionary<ulong, ulong> m_ObjectToOwnershipTable = new Dictionary<ulong, ulong>();
/// <summary>
/// Used to update a NetworkObject's ownership
/// </summary>
internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, bool isRemoving = false)
{ {
public FastBufferReader Reader; var previousOwner = newOwner;
public MessageHeader Header;
public ulong SenderId; // Use internal lookup table to see if the NetworkObject has a previous owner
public float Timestamp; if (m_ObjectToOwnershipTable.ContainsKey(networkObject.NetworkObjectId))
public int SerializedHeaderSize; {
} // Keep track of the previous owner's ClientId
private struct TriggerInfo previousOwner = m_ObjectToOwnershipTable[networkObject.NetworkObjectId];
{
public float Expiry; // We are either despawning (remove) or changing ownership (assign)
public NativeList<TriggerData> TriggerData; if (isRemoving)
{
m_ObjectToOwnershipTable.Remove(networkObject.NetworkObjectId);
}
else
{
m_ObjectToOwnershipTable[networkObject.NetworkObjectId] = newOwner;
}
}
else
{
// Otherwise, just add a new lookup entry
m_ObjectToOwnershipTable.Add(networkObject.NetworkObjectId, newOwner);
}
// Check to see if we had a previous owner
if (previousOwner != newOwner && OwnershipToObjectsTable.ContainsKey(previousOwner))
{
// Before updating the previous owner, assure this entry exists
if (OwnershipToObjectsTable[previousOwner].ContainsKey(networkObject.NetworkObjectId))
{
// Remove the previous owner's entry
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
// Server or Host alway invokes the lost ownership notification locally
if (NetworkManager.IsServer)
{
networkObject.InvokeBehaviourOnLostOwnership();
}
// If we are removing the entry (i.e. despawning or client lost ownership)
if (isRemoving)
{
return;
}
}
else
{
// Really, as long as UpdateOwnershipTable is invoked when ownership is gained or lost this should never happen
throw new Exception($"Client-ID {previousOwner} had a partial {nameof(m_ObjectToOwnershipTable)} entry! Potentially corrupted {nameof(OwnershipToObjectsTable)}?");
}
}
// If the owner doesn't have an entry then create one
if (!OwnershipToObjectsTable.ContainsKey(newOwner))
{
OwnershipToObjectsTable.Add(newOwner, new Dictionary<ulong, NetworkObject>());
}
// Sanity check to make sure we don't already have this entry (we shouldn't)
if (!OwnershipToObjectsTable[newOwner].ContainsKey(networkObject.NetworkObjectId))
{
// Add the new ownership entry
OwnershipToObjectsTable[newOwner].Add(networkObject.NetworkObjectId, networkObject);
// Server or Host always invokes the gained ownership notification locally
if (NetworkManager.IsServer)
{
networkObject.InvokeBehaviourOnGainedOwnership();
}
}
else if (isRemoving)
{
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"Setting ownership twice? Client-ID {previousOwner} already owns NetworkObject ID {networkObject.NetworkObjectId}!");
}
} }
private readonly Dictionary<ulong, TriggerInfo> m_Triggers = new Dictionary<ulong, TriggerInfo>(); /// <summary>
/// Returns a list of all NetworkObjects that belong to a client.
/// </summary>
/// <param name="clientId">the client's id <see cref="NetworkManager.LocalClientId"/></param>
public List<NetworkObject> GetClientOwnedObjects(ulong clientId)
{
if (!OwnershipToObjectsTable.ContainsKey(clientId))
{
OwnershipToObjectsTable.Add(clientId, new Dictionary<ulong, NetworkObject>());
}
return OwnershipToObjectsTable[clientId].Values.ToList();
}
/// <summary> /// <summary>
/// Gets the NetworkManager associated with this SpawnManager. /// Gets the NetworkManager associated with this SpawnManager.
@@ -93,88 +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, or with
/// snapshot spawns enabled where the spawn is sent unreliably and not until the end of the frame.
///
/// 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)
@@ -194,37 +210,21 @@ namespace Unity.Netcode
return; return;
} }
// Make sure the connected client entry exists before trying to remove ownership. // Server removes the entry and takes over ownership before notifying
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient)) UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
networkObject.OwnerClientId = NetworkManager.ServerClientId;
var message = new ChangeOwnershipMessage
{ {
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--) NetworkObjectId = networkObject.NetworkObjectId,
{ OwnerClientId = networkObject.OwnerClientId
if (networkClient.OwnedObjects[i] == networkObject) };
{ var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
networkClient.OwnedObjects.RemoveAt(i);
}
}
networkObject.OwnerClientIdInternal = null; foreach (var client in NetworkManager.ConnectedClients)
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
else
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
{
NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
}
} }
} }
@@ -265,25 +265,10 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is not spawned"); throw new SpawnStateException("Object is not spawned");
} }
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
for (int i = networkClient.OwnedObjects.Count - 1; i >= 0; i--)
{
if (networkClient.OwnedObjects[i] == networkObject)
{
networkClient.OwnedObjects.RemoveAt(i);
}
}
networkClient.OwnedObjects.Add(networkObject);
}
networkObject.OwnerClientId = clientId; networkObject.OwnerClientId = clientId;
if (TryGetNetworkClient(clientId, out NetworkClient newNetworkClient)) // Server adds entries for all client ownership
{ UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
newNetworkClient.OwnedObjects.Add(networkObject);
}
var message = new ChangeOwnershipMessage var message = new ChangeOwnershipMessage
{ {
@@ -298,6 +283,33 @@ namespace Unity.Netcode
} }
} }
internal bool HasPrefab(bool isSceneObject, uint globalObjectIdHash)
{
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
{
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
{
return true;
}
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, 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(globalObjectIdHash);
return networkObject != null;
}
/// <summary> /// <summary>
/// Should only run on the client /// Should only run on the client
/// </summary> /// </summary>
@@ -414,7 +426,7 @@ namespace Unity.Netcode
} }
// Ran on both server and client // Ran on both server and client
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene) internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
{ {
if (networkObject == null) if (networkObject == null)
{ {
@@ -452,15 +464,12 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is already spawned"); throw new SpawnStateException("Object is already spawned");
} }
if (sceneObject.Header.HasNetworkVariables) networkObject.SetNetworkVariableData(variableData);
{
networkObject.SetNetworkVariableData(variableData);
}
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene); SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene);
} }
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene) private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
{ {
if (SpawnedObjects.ContainsKey(networkId)) if (SpawnedObjects.ContainsKey(networkId))
{ {
@@ -471,38 +480,45 @@ namespace Unity.Netcode
// this initialization really should be at the bottom of the function // 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 // 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 // NetworkVariable dependency on NetworkBehaviour, this otherwise creates problems because
// SetNetworkVariableData above calls InitializeVariables, and the 'baked out' data isn't ready there; // 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 // the current design banks on getting the network behaviour set and then only reading from it after the
// after the below initialization code. However cowardice compels me to hold off on moving this until // below initialization code. However cowardice compels me to hold off on moving this until that commit
// that commit
networkObject.IsSceneObject = sceneObject; networkObject.IsSceneObject = sceneObject;
networkObject.NetworkObjectId = networkId; networkObject.NetworkObjectId = networkId;
networkObject.DestroyWithScene = sceneObject || destroyWithScene; networkObject.DestroyWithScene = sceneObject || destroyWithScene;
networkObject.OwnerClientIdInternal = ownerClientId; networkObject.OwnerClientId = ownerClientId;
networkObject.IsPlayerObject = playerObject; networkObject.IsPlayerObject = playerObject;
SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject); SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject);
SpawnedObjectsList.Add(networkObject); SpawnedObjectsList.Add(networkObject);
if (ownerClientId != null) if (NetworkManager.IsServer)
{ {
if (NetworkManager.IsServer) if (playerObject)
{ {
if (playerObject) // If there was an already existing player object for this player, then mark it as no longer
// a player object.
if (NetworkManager.ConnectedClients[ownerClientId].PlayerObject != null)
{ {
NetworkManager.ConnectedClients[ownerClientId.Value].PlayerObject = networkObject; NetworkManager.ConnectedClients[ownerClientId].PlayerObject.IsPlayerObject = false;
}
else
{
NetworkManager.ConnectedClients[ownerClientId.Value].OwnedObjects.Add(networkObject);
} }
NetworkManager.ConnectedClients[ownerClientId].PlayerObject = networkObject;
} }
else if (playerObject && ownerClientId.Value == NetworkManager.LocalClientId) }
else if (ownerClientId == NetworkManager.LocalClientId)
{
if (playerObject)
{ {
// If there was an already existing player object for this player, then mark it as no longer a player object.
if (NetworkManager.LocalClient.PlayerObject != null)
{
NetworkManager.LocalClient.PlayerObject.IsPlayerObject = false;
}
NetworkManager.LocalClient.PlayerObject = networkObject; NetworkManager.LocalClient.PlayerObject = networkObject;
} }
} }
@@ -524,20 +540,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>();
@@ -549,25 +552,21 @@ namespace Unity.Netcode
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject) internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
{ {
if (!NetworkManager.NetworkConfig.UseSnapshotSpawn) //Currently, if this is called and the clientId (destination) is the server's client Id, this case will be checked
// within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer placing this check here. [NSS]
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
{ {
//Currently, if this is called and the clientId (destination) is the server's client Id, this case return;
//will be checked within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer
//placing this check here. [NSS]
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
{
return;
}
var message = new CreateObjectMessage
{
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty();
} }
var message = new CreateObjectMessage
{
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty();
} }
internal ulong? GetSpawnParentId(NetworkObject networkObject) internal ulong? GetSpawnParentId(NetworkObject networkObject)
@@ -605,14 +604,12 @@ namespace Unity.Netcode
// Makes scene objects ready to be reused // Makes scene objects ready to be reused
internal void ServerResetShudownStateForSceneObjects() internal void ServerResetShudownStateForSceneObjects()
{ {
foreach (var sobj in SpawnedObjectsList) var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
foreach (var sobj in networkObjects)
{ {
if ((sobj.IsSceneObject != null && sobj.IsSceneObject == true) || sobj.DestroyWithScene) sobj.IsSpawned = false;
{ sobj.DestroyWithScene = false;
sobj.IsSpawned = false; sobj.IsSceneObject = null;
sobj.DestroyWithScene = false;
sobj.IsSceneObject = null;
}
} }
} }
@@ -653,14 +650,12 @@ namespace Unity.Netcode
else if (networkObjects[i].IsSpawned) else if (networkObjects[i].IsSpawned)
{ {
// If it is an in-scene placed NetworkObject then just despawn // If it is an in-scene placed NetworkObject then just despawn
// and let it be destroyed when the scene is unloaded. Otherwise, // and let it be destroyed when the scene is unloaded. Otherwise, despawn and destroy it.
// despawn and destroy it. var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value);
var shouldDestroy = !(networkObjects[i].IsSceneObject != null
&& networkObjects[i].IsSceneObject.Value);
OnDespawnObject(networkObjects[i], shouldDestroy); OnDespawnObject(networkObjects[i], shouldDestroy);
} }
else else if (networkObjects[i].IsSceneObject != null && !networkObjects[i].IsSceneObject.Value)
{ {
UnityEngine.Object.Destroy(networkObjects[i].gameObject); UnityEngine.Object.Destroy(networkObjects[i].gameObject);
} }
@@ -711,9 +706,10 @@ namespace Unity.Netcode
} }
} }
foreach (var networkObject in networkObjectsToSpawn) foreach (var networkObject in networkObjectsToSpawn)
{ {
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, null, true); SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, networkObject.OwnerClientId, true);
} }
} }
@@ -757,18 +753,6 @@ namespace Unity.Netcode
} }
} }
if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
//Someone owns it.
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
{
if (networkClient.OwnedObjects[i].NetworkObjectId == networkObject.NetworkObjectId)
{
networkClient.OwnedObjects.RemoveAt(i);
}
}
}
networkObject.InvokeBehaviourNetworkDespawn(); networkObject.InvokeBehaviourNetworkDespawn();
if (NetworkManager != null && NetworkManager.IsServer) if (NetworkManager != null && NetworkManager.IsServer)
@@ -782,38 +766,31 @@ namespace Unity.Netcode
}); });
} }
if (NetworkManager.NetworkConfig.UseSnapshotSpawn) if (networkObject != null)
{ {
networkObject.SnapshotDespawn(); // As long as we have any remaining clients, then notify of the object being destroy.
} if (NetworkManager.ConnectedClientsList.Count > 0)
else
{
if (networkObject != null)
{ {
// As long as we have any remaining clients, then notify of the object being destroy. m_TargetClientIds.Clear();
if (NetworkManager.ConnectedClientsList.Count > 0)
// We keep only the client for which the object is visible
// as the other clients have them already despawned
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{ {
m_TargetClientIds.Clear(); if (networkObject.IsNetworkVisibleTo(clientId))
// We keep only the client for which the object is visible
// as the other clients have them already despawned
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{ {
if (networkObject.IsNetworkVisibleTo(clientId)) m_TargetClientIds.Add(clientId);
{
m_TargetClientIds.Add(clientId);
}
} }
}
var message = new DestroyObjectMessage var message = new DestroyObjectMessage
{ {
NetworkObjectId = networkObject.NetworkObjectId NetworkObjectId = networkObject.NetworkObjectId
}; };
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)
{ {
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size); NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
}
} }
} }
} }

View File

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

View File

@@ -1,54 +0,0 @@
#if UNITY_UNET_PRESENT
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Networking;
namespace Unity.Netcode.Transports.UNET
{
/// <summary>
/// A transport channel used by the netcode
/// </summary>
[Serializable]
public class UNetChannel
{
/// <summary>
/// The name of the channel
/// </summary>
#if UNITY_EDITOR
[ReadOnly]
#endif
public byte Id;
/// <summary>
/// The type of channel
/// </summary>
public QosType Type;
#if UNITY_EDITOR
private class ReadOnlyAttribute : PropertyAttribute { }
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
private class ReadOnlyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Saving previous GUI enabled value
var previousGUIState = GUI.enabled;
// Disabling edit for property
GUI.enabled = false;
// Drawing Property
EditorGUI.PropertyField(position, property, label);
// Setting old GUI enabled value
GUI.enabled = previousGUIState;
}
}
#endif
}
}
#endif

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6b1ef235ca94b4bbd9a6456f44c69188 guid: 81887adf6d9ca40c9b70728b7018b6f5
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@@ -0,0 +1,96 @@
using System;
using Unity.Networking.Transport;
namespace Unity.Netcode.Transports.UTP
{
/// <summary>Queue for batched messages received through UTP.</summary>
/// <remarks>This is meant as a companion to <see cref="BatchedSendQueue"/>.</remarks>
internal class BatchedReceiveQueue
{
private byte[] m_Data;
private int m_Offset;
private int m_Length;
public bool IsEmpty => m_Length <= 0;
/// <summary>
/// Construct a new receive queue from a <see cref="DataStreamReader"/> returned by
/// <see cref="NetworkDriver"/> when popping a data event.
/// </summary>
/// <param name="reader">The <see cref="DataStreamReader"/> to construct from.</param>
public BatchedReceiveQueue(DataStreamReader reader)
{
m_Data = new byte[reader.Length];
unsafe
{
fixed (byte* dataPtr = m_Data)
{
reader.ReadBytes(dataPtr, reader.Length);
}
}
m_Offset = 0;
m_Length = reader.Length;
}
/// <summary>
/// Push the entire data from a <see cref="DataStreamReader"/> (as returned by popping an
/// event from a <see cref="NetworkDriver">) to the queue.
/// </summary>
/// <param name="reader">The <see cref="DataStreamReader"/> to push the data of.</param>
public void PushReader(DataStreamReader reader)
{
// Resize the array and copy the existing data to the beginning if there's not enough
// room to copy the reader's data at the end of the existing data.
var available = m_Data.Length - (m_Offset + m_Length);
if (available < reader.Length)
{
if (m_Length > 0)
{
Array.Copy(m_Data, m_Offset, m_Data, 0, m_Length);
}
m_Offset = 0;
while (m_Data.Length - m_Length < reader.Length)
{
Array.Resize(ref m_Data, m_Data.Length * 2);
}
}
unsafe
{
fixed (byte* dataPtr = m_Data)
{
reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length);
}
}
m_Length += reader.Length;
}
/// <summary>Pop the next full message in the queue.</summary>
/// <returns>The message, or the default value if no more full messages.</returns>
public ArraySegment<byte> PopMessage()
{
if (m_Length < sizeof(int))
{
return default;
}
var messageLength = BitConverter.ToInt32(m_Data, m_Offset);
if (m_Length - sizeof(int) < messageLength)
{
return default;
}
var data = new ArraySegment<byte>(m_Data, m_Offset + sizeof(int), messageLength);
m_Offset += sizeof(int) + messageLength;
m_Length -= sizeof(int) + messageLength;
return data;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 69c3c1c5a885d4aed99ee2e1fa40f763 guid: e9ead10b891184bd5b8f2650fd66a5b1
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,233 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Networking.Transport;
namespace Unity.Netcode.Transports.UTP
{
/// <summary>Queue for batched messages meant to be sent through UTP.</summary>
/// <remarks>
/// Messages should be pushed on the queue with <see cref="PushMessage"/>. To send batched
/// messages, call <see cref="FillWriter"> with the <see cref="DataStreamWriter"/> obtained from
/// <see cref="NetworkDriver.BeginSend"/>. This will fill the writer with as many messages as
/// possible. If the send is successful, call <see cref="Consume"/> to remove the data from the
/// queue.
///
/// This is meant as a companion to <see cref="BatchedReceiveQueue"/>, which should be used to
/// read messages sent with this queue.
/// </remarks>
internal struct BatchedSendQueue : IDisposable
{
private NativeArray<byte> m_Data;
private NativeArray<int> m_HeadTailIndices;
/// <summary>Overhead that is added to each message in the queue.</summary>
public const int PerMessageOverhead = sizeof(int);
// Indices into m_HeadTailIndicies.
private const int k_HeadInternalIndex = 0;
private const int k_TailInternalIndex = 1;
/// <summary>Index of the first byte of the oldest data in the queue.</summary>
private int HeadIndex
{
get { return m_HeadTailIndices[k_HeadInternalIndex]; }
set { m_HeadTailIndices[k_HeadInternalIndex] = value; }
}
/// <summary>Index one past the last byte of the most recent data in the queue.</summary>
private int TailIndex
{
get { return m_HeadTailIndices[k_TailInternalIndex]; }
set { m_HeadTailIndices[k_TailInternalIndex] = value; }
}
public int Length => TailIndex - HeadIndex;
public bool IsEmpty => HeadIndex == TailIndex;
public bool IsCreated => m_Data.IsCreated;
/// <summary>Construct a new empty send queue.</summary>
/// <param name="capacity">Maximum capacity of the send queue.</param>
public BatchedSendQueue(int capacity)
{
m_Data = new NativeArray<byte>(capacity, Allocator.Persistent);
m_HeadTailIndices = new NativeArray<int>(2, Allocator.Persistent);
HeadIndex = 0;
TailIndex = 0;
}
public void Dispose()
{
if (IsCreated)
{
m_Data.Dispose();
m_HeadTailIndices.Dispose();
}
}
/// <summary>Append data at the tail of the queue. No safety checks.</summary>
private void AppendDataAtTail(ArraySegment<byte> data)
{
unsafe
{
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, m_Data.Length - TailIndex);
writer.WriteInt(data.Count);
fixed (byte* dataPtr = data.Array)
{
writer.WriteBytes(dataPtr + data.Offset, data.Count);
}
}
TailIndex += sizeof(int) + data.Count;
}
/// <summary>Append a new message to the queue.</summary>
/// <param name="message">Message to append to the queue.</param>
/// <returns>
/// Whether the message was appended successfully. The only way it can fail is if there's
/// no more room in the queue. On failure, nothing is written to the queue.
/// </returns>
public bool PushMessage(ArraySegment<byte> message)
{
if (!IsCreated)
{
return false;
}
// Check if there's enough room after the current tail index.
if (m_Data.Length - TailIndex >= sizeof(int) + message.Count)
{
AppendDataAtTail(message);
return true;
}
// Check if there would be enough room if we moved data at the beginning of m_Data.
if (m_Data.Length - TailIndex + HeadIndex >= sizeof(int) + message.Count)
{
// Move the data back at the beginning of m_Data.
unsafe
{
UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
}
TailIndex = Length;
HeadIndex = 0;
AppendDataAtTail(message);
return true;
}
return false;
}
/// <summary>
/// Fill as much of a <see cref="DataStreamWriter"/> as possible with data from the head of
/// the queue. Only full messages (and their length) are written to the writer.
/// </summary>
/// <remarks>
/// This does NOT actually consume anything from the queue. That is, calling this method
/// does not reduce the length of the queue. Callers are expected to call
/// <see cref="Consume"/> with the value returned by this method afterwards if the data can
/// be safely removed from the queue (e.g. if it was sent successfully).
///
/// This method should not be used together with <see cref="FillWriterWithBytes"> since this
/// could lead to a corrupted queue.
/// </remarks>
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
/// <returns>How many bytes were written to the writer.</returns>
public int FillWriterWithMessages(ref DataStreamWriter writer)
{
if (!IsCreated || Length == 0)
{
return 0;
}
unsafe
{
var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
var writerAvailable = writer.Capacity;
var readerOffset = 0;
while (readerOffset < Length)
{
reader.SeekSet(readerOffset);
var messageLength = reader.ReadInt();
if (writerAvailable < sizeof(int) + messageLength)
{
break;
}
else
{
writer.WriteInt(messageLength);
var messageOffset = HeadIndex + reader.GetBytesRead();
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
writerAvailable -= sizeof(int) + messageLength;
readerOffset += sizeof(int) + messageLength;
}
}
return writer.Capacity - writerAvailable;
}
}
/// <summary>
/// Fill the given <see cref="DataStreamWriter"/> with as many bytes from the queue as
/// possible, disregarding message boundaries.
/// </summary>
///<remarks>
/// This does NOT actually consume anything from the queue. That is, calling this method
/// does not reduce the length of the queue. Callers are expected to call
/// <see cref="Consume"/> with the value returned by this method afterwards if the data can
/// be safely removed from the queue (e.g. if it was sent successfully).
///
/// This method should not be used together with <see cref="FillWriterWithMessages"/> since
/// this could lead to reading messages from a corrupted queue.
/// </remarks>
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
/// <returns>How many bytes were written to the writer.</returns>
public int FillWriterWithBytes(ref DataStreamWriter writer)
{
if (!IsCreated || Length == 0)
{
return 0;
}
var copyLength = Math.Min(writer.Capacity, Length);
unsafe
{
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
}
return copyLength;
}
/// <summary>Consume a number of bytes from the head of the queue.</summary>
/// <remarks>
/// This should only be called with a size that matches the last value returned by
/// <see cref="FillWriter"/>. Anything else will result in a corrupted queue.
/// </remarks>
/// <param name="size">Number of bytes to consume from the queue.</param>
public void Consume(int size)
{
if (size >= Length)
{
HeadIndex = 0;
TailIndex = 0;
}
else
{
HeadIndex += size;
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a32aeecf69a2542469927066f5b88005 guid: ddf8f97f695d740f297dc42242b76b8c
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,8 @@
namespace Unity.Netcode.Transports.UTP
{
public struct NetworkMetricsContext
{
public uint PacketSentCount;
public uint PacketReceivedCount;
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: c275febadb27c4d18b41218e3353b84b guid: adb0270501ff1421896ce15cc75bd56a
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,70 @@
#if MULTIPLAYER_TOOLS
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
using AOT;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Networking.Transport;
using UnityEngine;
namespace Unity.Netcode.Transports.UTP
{
[BurstCompile]
internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage
{
static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer,
int staticInstanceBufferLength,
NetworkSettings settings)
{
return new NetworkPipelineStage(
ReceiveFunction,
SendFunction,
InitializeConnectionFunction,
ReceiveCapacity: 0,
SendCapacity: 0,
HeaderCapacity: 0,
SharedStateCapacity: UnsafeUtility.SizeOf<NetworkMetricsContext>());
}
public int StaticSize => 0;
[BurstCompile(DisableDirectCall = true)]
[MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))]
private static void Receive(ref NetworkPipelineContext networkPipelineContext,
ref InboundRecvBuffer inboundReceiveBuffer,
ref NetworkPipelineStage.Requests requests,
int systemHeaderSize)
{
var networkMetricContext = (NetworkMetricsContext*)networkPipelineContext.internalSharedProcessBuffer;
networkMetricContext->PacketReceivedCount++;
}
[BurstCompile(DisableDirectCall = true)]
[MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))]
private static int Send(ref NetworkPipelineContext networkPipelineContext,
ref InboundSendBuffer inboundSendBuffer,
ref NetworkPipelineStage.Requests requests,
int systemHeaderSize)
{
var networkMetricContext = (NetworkMetricsContext*)networkPipelineContext.internalSharedProcessBuffer;
networkMetricContext->PacketSentCount++;
return 0;
}
[BurstCompile(DisableDirectCall = true)]
[MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))]
private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength,
byte* sendProcessBuffer, int sendProcessBufferLength, byte* receiveProcessBuffer, int receiveProcessBufferLength,
byte* sharedProcessBuffer, int sharedProcessBufferLength)
{
var networkMetricContext = (NetworkMetricsContext*)sharedProcessBuffer;
networkMetricContext->PacketSentCount = 0;
networkMetricContext->PacketReceivedCount = 0;
}
}
}
#endif
#endif

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -10,7 +10,9 @@
"Unity.Multiplayer.Tools.NetStats", "Unity.Multiplayer.Tools.NetStats",
"Unity.Multiplayer.Tools.NetStatsReporting", "Unity.Multiplayer.Tools.NetStatsReporting",
"Unity.Multiplayer.Tools.NetworkSolutionInterface", "Unity.Multiplayer.Tools.NetworkSolutionInterface",
"Unity.Collections" "Unity.Networking.Transport",
"Unity.Collections",
"Unity.Burst"
], ],
"allowUnsafeCode": true, "allowUnsafeCode": true,
"versionDefines": [ "versionDefines": [
@@ -26,8 +28,8 @@
}, },
{ {
"name": "com.unity.multiplayer.tools", "name": "com.unity.multiplayer.tools",
"expression": "1.0.0-pre.4", "expression": "1.0.0-pre.7",
"define": "MULTIPLAYER_TOOLS_1_0_0_PRE_4" "define": "MULTIPLAYER_TOOLS_1_0_0_PRE_7"
} }
] ]
} }

View File

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

View File

@@ -1,4 +0,0 @@
{
"displayName": "ClientNetworkTransform",
"description": "A sample to demonstrate how client-driven NetworkTransform can be implemented"
}

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