com.unity.netcode.gameobjects@1.7.0

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.7.0] - 2023-10-11

### Added

- exposed NetworkObject.GetNetworkBehaviourAtOrderIndex as a public API (#2724)
- Added context menu tool that provides users with the ability to quickly update the GlobalObjectIdHash value for all in-scene placed prefab instances that were created prior to adding a NetworkObject component to it. (#2707)
- Added methods NetworkManager.SetPeerMTU and NetworkManager.GetPeerMTU to be able to set MTU sizes per-peer (#2676)
- Added `GenerateSerializationForGenericParameterAttribute`, which can be applied to user-created Network Variable types to ensure the codegen generates serialization for the generic types they wrap. (#2694)
- Added `GenerateSerializationForTypeAttribute`, which can be applied to any class or method to ensure the codegen generates serialization for the specific provided type. (#2694)
- Exposed `NetworkVariableSerialization<T>.Read`, `NetworkVariableSerialization<T>.Write`, `NetworkVariableSerialization<T>.AreEqual`, and `NetworkVariableSerialization<T>.Duplicate` to further support the creation of user-created network variables by allowing users to access the generated serialization methods and serialize generic types efficiently without boxing. (#2694)
- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694)

### Fixed

- Fixed issue where the server side `NetworkSceneManager` instance was not adding the currently active scene to its list of scenes loaded. (#2723)
- Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720)
- Rpcs within Generic NetworkBehaviour types can now serialize parameters of the class's generic types (but may not have generic types of their own) (#2720)
- Errors are no longer thrown when entering play mode with domain reload disabled (#2720)
- NetworkSpawn is now correctly called each time when entering play mode with scene reload disabled (#2720)
- NetworkVariables of non-integer types will no longer break the inspector (#2714)
- NetworkVariables with NonSerializedAttribute will not appear in the inspector (#2714)
- Fixed issue where `UnityTransport` would attempt to establish WebSocket connections even if using UDP/DTLS Relay allocations when the build target was WebGL. This only applied to working in the editor since UDP/DTLS can't work in the browser. (#2695)
- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685)
- Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682)
- Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674)
- Fixed "writing past the end of the buffer" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670)
- Fixed issue where generation of the `DefaultNetworkPrefabs` asset was not enabled by default. (#2662)
- Fixed issue where the `GlobalObjectIdHash` value could be updated but the asset not marked as dirty. (#2662)
- Fixed issue where the `GlobalObjectIdHash` value of a (network) prefab asset could be assigned an incorrect value when editing the prefab in a temporary scene. (#2662)
- Fixed issue where the `GlobalObjectIdHash` value generated after creating a (network) prefab from an object constructed within the scene would not be the correct final value in a stand alone build. (#2662)

### Changed

- Updated dependency on `com.unity.transport` to version 1.4.0. (#2716)
This commit is contained in:
Unity Technologies
2023-10-11 00:00:00 +00:00
parent b3bd4727ab
commit ffef45b50f
43 changed files with 1783 additions and 322 deletions

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
@@ -11,6 +12,18 @@ namespace Unity.Netcode
public abstract class NetworkBehaviour : MonoBehaviour
{
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `public`
internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, RpcReceiveHandler>> __rpc_func_table = new Dictionary<Type, Dictionary<uint, RpcReceiveHandler>>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, string>> __rpc_name_table = new Dictionary<Type, Dictionary<uint, string>>();
#endif
// RuntimeAccessModifiersILPP will make this `protected`
internal enum __RpcExecStage
{
@@ -97,7 +110,7 @@ namespace Unity.Netcode
bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
{
NetworkManager.NetworkMetrics.TrackRpcSent(
NetworkManager.ServerClientId,
@@ -228,7 +241,7 @@ namespace Unity.Netcode
bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
{
if (clientRpcParams.Send.TargetClientIds != null)
{
@@ -532,6 +545,23 @@ namespace Unity.Netcode
OnGainedOwnership();
}
/// <summary>
/// Invoked on all clients, override this method to be notified of any
/// ownership changes (even if the instance was niether the previous or
/// newly assigned current owner).
/// </summary>
/// <param name="previous">the previous owner</param>
/// <param name="current">the current owner</param>
protected virtual void OnOwnershipChanged(ulong previous, ulong current)
{
}
internal void InternalOnOwnershipChanged(ulong previous, ulong current)
{
OnOwnershipChanged(previous, current);
}
/// <summary>
/// Gets called when we loose ownership of this object
/// </summary>
@@ -565,6 +595,25 @@ namespace Unity.Netcode
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's network variables.
}
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal virtual void __initializeRpcs()
#pragma warning restore IDE1006 // restore naming rule violation check
{
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's RPCs.
}
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName)
#pragma warning restore IDE1006 // restore naming rule violation check
{
__rpc_func_table[GetType()][hash] = handler;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
__rpc_name_table[GetType()][hash] = rpcMethodName;
#endif
}
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
// Using this method here because ILPP doesn't seem to let us do visibility modification on properties.
@@ -583,6 +632,14 @@ namespace Unity.Netcode
m_VarInit = true;
if (!__rpc_func_table.ContainsKey(GetType()))
{
__rpc_func_table[GetType()] = new Dictionary<uint, RpcReceiveHandler>();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
__rpc_name_table[GetType()] = new Dictionary<uint, string>();
#endif
__initializeRpcs();
}
__initializeVariables();
{

View File

@@ -15,6 +15,9 @@ namespace Unity.Netcode
[AddComponentMenu("Netcode/Network Manager", -100)]
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
{
// TODO: Deprecate...
// The following internal values are not used, but because ILPP makes them public in the assembly, they cannot
// be removed thanks to our semver validation.
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `public`
@@ -491,6 +494,15 @@ namespace Unity.Netcode
}
}
}
private void ModeChanged(PlayModeStateChange change)
{
if (IsListening && change == PlayModeStateChange.ExitingPlayMode)
{
// Make sure we are not holding onto anything in case domain reload is disabled
ShutdownInternal();
}
}
#endif
/// <summary>
@@ -539,6 +551,9 @@ namespace Unity.Netcode
NetworkConfig?.InitializePrefabs();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += ModeChanged;
#endif
}
private void OnEnable()
@@ -581,7 +596,12 @@ namespace Unity.Netcode
/// <summary>
/// Sets the maximum size of a single non-fragmented message (or message batch) passed through the transport.
/// This should represent the transport's MTU size, minus any transport-level overhead.
/// This should represent the transport's default MTU size, minus any transport-level overhead.
/// This value will be used for any remote endpoints that haven't had per-endpoint MTUs set.
/// This value is also used as the size of the temporary buffer used when serializing
/// a single message (to avoid serializing multiple times when sending to multiple endpoints),
/// and thus should be large enough to ensure it can hold each message type.
/// This value defaults to 1296.
/// </summary>
/// <param name="size"></param>
public int MaximumTransmissionUnitSize
@@ -590,6 +610,34 @@ namespace Unity.Netcode
get => MessageManager.NonFragmentedMessageMaxSize;
}
/// <summary>
/// Set the maximum transmission unit for a specific peer.
/// This determines the maximum size of a message batch that can be sent to that client.
/// If not set for any given client, <see cref="MaximumTransmissionUnitSize"/> will be used instead.
/// </summary>
/// <param name="clientId"></param>
/// <param name="size"></param>
public void SetPeerMTU(ulong clientId, int size)
{
MessageManager.PeerMTUSizes[clientId] = size;
}
/// <summary>
/// Queries the current MTU size for a client.
/// If no MTU has been set for that client, will return <see cref="MaximumTransmissionUnitSize"/>
/// </summary>
/// <param name="clientId"></param>
/// <returns></returns>
public int GetPeerMTU(ulong clientId)
{
if (MessageManager.PeerMTUSizes.TryGetValue(clientId, out var ret))
{
return ret;
}
return MessageManager.NonFragmentedMessageMaxSize;
}
/// <summary>
/// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery.
/// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size.

View File

@@ -1,9 +1,18 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
#if UNITY_EDITOR
using UnityEditor;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#else
using UnityEditor.Experimental.SceneManagement;
#endif
#endif
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
@@ -37,30 +46,171 @@ namespace Unity.Netcode
}
}
private bool m_IsPrefab;
#if UNITY_EDITOR
private void OnValidate()
private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}";
/// <summary>
/// Object Types <see href="https://docs.unity3d.com/ScriptReference/GlobalObjectId.html"/>
/// Parameter 0 of <see cref="k_GlobalIdTemplate"/>
/// </summary>
// 0 = Null (when considered a null object type we can ignore)
// 1 = Imported Asset
// 2 = Scene Object
// 3 = Source Asset.
private const int k_NullObjectType = 0;
private const int k_ImportedAssetObjectType = 1;
private const int k_SceneObjectType = 2;
private const int k_SourceAssetObjectType = 3;
[ContextMenu("Refresh In-Scene Prefab Instances")]
internal void RefreshAllPrefabInstances()
{
GenerateGlobalObjectIdHash();
var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
if (!PrefabUtility.IsPartOfAnyPrefab(this) || instanceGlobalId.identifierType != k_ImportedAssetObjectType)
{
EditorUtility.DisplayDialog("Network Prefab Assets Only", "This action can only be performed on a network prefab asset.", "Ok");
return;
}
// Handle updating the currently active scene
var networkObjects = FindObjectsByType<NetworkObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var networkObject in networkObjects)
{
networkObject.OnValidate();
}
NetworkObjectRefreshTool.ProcessActiveScene();
// Refresh all build settings scenes
var activeScene = SceneManager.GetActiveScene();
foreach (var editorScene in EditorBuildSettings.scenes)
{
// skip disabled scenes and the currently active scene
if (!editorScene.enabled || activeScene.path == editorScene.path)
{
continue;
}
// Add the scene to be processed
NetworkObjectRefreshTool.ProcessScene(editorScene.path, false);
}
// Process all added scenes
NetworkObjectRefreshTool.ProcessScenes();
}
internal void GenerateGlobalObjectIdHash()
private void OnValidate()
{
// do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode
if (UnityEditor.EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
{
return;
}
// do NOT regenerate GlobalObjectIdHash if Editor is transitioning into or out of PlayMode
if (!UnityEditor.EditorApplication.isPlaying && UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
var globalObjectIdString = UnityEditor.GlobalObjectId.GetGlobalObjectIdSlow(this).ToString();
GlobalObjectIdHash = XXHash.Hash32(globalObjectIdString);
// Get a global object identifier for this network prefab
var globalId = GetGlobalId();
// if the identifier type is 0, then don't update the GlobalObjectIdHash
if (globalId.identifierType == k_NullObjectType)
{
return;
}
var oldValue = GlobalObjectIdHash;
GlobalObjectIdHash = globalId.ToString().Hash32();
// If the GlobalObjectIdHash value changed, then mark the asset dirty
if (GlobalObjectIdHash != oldValue)
{
// Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed)
if (!IsEditingPrefab() && gameObject.scene.name != null && gameObject.scene.name != gameObject.name)
{
// Sanity check to make sure this is a scene placed object
if (globalId.identifierType != k_SceneObjectType)
{
// This should never happen, but in the event it does throw and error
Debug.LogError($"[{gameObject.name}] is detected as an in-scene placed object but its identifier is of type {globalId.identifierType}! **Report this error**");
}
// If this is a prefab instance
if (PrefabUtility.IsPartOfAnyPrefab(this))
{
// We must invoke this in order for the modifications to get saved with the scene (does not mark scene as dirty)
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
}
}
else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it
{
EditorUtility.SetDirty(this);
}
}
}
private bool IsEditingPrefab()
{
// Check if we are directly editing the prefab
var stage = PrefabStageUtility.GetPrefabStage(gameObject);
// if we are not editing the prefab directly (or a sub-prefab), then return the object identifier
if (stage == null || stage.assetPath == null)
{
return false;
}
return true;
}
private GlobalObjectId GetGlobalId()
{
var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
// If not editing a prefab, then just use the generated id
if (!IsEditingPrefab())
{
return instanceGlobalId;
}
// If the asset doesn't exist at the given path, then return the object identifier
var prefabStageAssetPath = PrefabStageUtility.GetPrefabStage(gameObject).assetPath;
// If (for some reason) the asset path is null return the generated id
if (prefabStageAssetPath == null)
{
return instanceGlobalId;
}
var theAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(prefabStageAssetPath);
// If there is no asset at that path (for some odd/edge case reason), return the generated id
if (theAsset == null)
{
return instanceGlobalId;
}
// If we can't get the asset GUID and/or the file identifier, then return the object identifier
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(theAsset, out var guid, out long localFileId))
{
return instanceGlobalId;
}
// Note: If we reached this point, then we are most likely opening a prefab to edit.
// The instanceGlobalId will be constructed as if it is a scene object, however when it
// is serialized its value will be treated as a file asset (the "why" to the below code).
// Construct an imported asset identifier with the type being a source asset object type
var prefabGlobalIdText = string.Format(k_GlobalIdTemplate, k_SourceAssetObjectType, guid, (ulong)localFileId, 0);
// If we can't parse the result log an error and return the instanceGlobalId
if (!GlobalObjectId.TryParse(prefabGlobalIdText, out var prefabGlobalId))
{
Debug.LogError($"[GlobalObjectId Gen] Failed to parse ({prefabGlobalIdText}) returning default ({instanceGlobalId})! ** Please Report This Error **");
return instanceGlobalId;
}
// Otherwise, return the constructed identifier for the source prefab asset
return prefabGlobalId;
}
#endif // UNITY_EDITOR
@@ -684,6 +834,21 @@ namespace Unity.Netcode
}
}
internal void InvokeOwnershipChanged(ulong previous, ulong next)
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].InternalOnOwnershipChanged(previous, next);
}
else
{
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
}
}
}
internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
@@ -1190,7 +1355,7 @@ namespace Unity.Netcode
return 0;
}
internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index)
public NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index)
{
if (index >= ChildNetworkBehaviours.Count)
{

View File

@@ -0,0 +1,118 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
/// This is a helper tool to update all in-scene placed instances of a prefab that
/// originally did not have a NetworkObject component but one was added to the prefab
/// later.
/// </summary>
internal class NetworkObjectRefreshTool
{
private static List<string> s_ScenesToUpdate = new List<string>();
private static bool s_ProcessScenes;
private static bool s_CloseScenes;
internal static Action AllScenesProcessed;
internal static void ProcessScene(string scenePath, bool processScenes = true)
{
if (!s_ScenesToUpdate.Contains(scenePath))
{
if (s_ScenesToUpdate.Count == 0)
{
EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened;
EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved;
}
s_ScenesToUpdate.Add(scenePath);
}
s_ProcessScenes = processScenes;
}
internal static void ProcessActiveScene()
{
var activeScene = SceneManager.GetActiveScene();
if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes)
{
SceneOpened(activeScene);
}
}
internal static void ProcessScenes()
{
if (s_ScenesToUpdate.Count != 0)
{
s_CloseScenes = true;
var scenePath = s_ScenesToUpdate.First();
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
}
else
{
s_CloseScenes = false;
EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved;
EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened;
AllScenesProcessed?.Invoke();
}
}
private static void FinishedProcessingScene(Scene scene, bool refreshed = false)
{
if (s_ScenesToUpdate.Contains(scene.path))
{
// Provide a log of all scenes that were modified to the user
if (refreshed)
{
Debug.Log($"Refreshed and saved updates to scene: {scene.name}");
}
s_ProcessScenes = false;
s_ScenesToUpdate.Remove(scene.path);
if (scene != SceneManager.GetActiveScene())
{
EditorSceneManager.CloseScene(scene, s_CloseScenes);
}
ProcessScenes();
}
}
private static void EditorSceneManager_sceneSaved(Scene scene)
{
FinishedProcessingScene(scene, true);
}
private static void SceneOpened(Scene scene)
{
if (s_ScenesToUpdate.Contains(scene.path))
{
if (s_ProcessScenes)
{
if (!EditorSceneManager.MarkSceneDirty(scene))
{
Debug.Log($"Scene {scene.name} did not get marked as dirty!");
FinishedProcessingScene(scene);
}
else
{
EditorSceneManager.SaveScene(scene);
}
}
else
{
FinishedProcessingScene(scene);
}
}
}
private static void EditorSceneManager_sceneOpened(Scene scene, OpenSceneMode mode)
{
SceneOpened(scene);
}
}
}
#endif // UNITY_EDITOR

View File

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

View File

@@ -0,0 +1,82 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Marks a generic parameter in this class as a type that should be serialized through
/// <see cref="NetworkVariableSerialization{T}"/>. This enables the use of the following methods to support
/// serialization within a Network Variable type:
/// <br/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Read"/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Write"/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.AreEqual"/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Duplicate"/>
/// <br/>
/// <br/>
/// The parameter is indicated by index (and is 0-indexed); for example:
/// <br/>
/// <code>
/// [SerializesGenericParameter(1)]
/// public class MyClass&lt;TTypeOne, TTypeTwo&gt;
/// {
/// }
/// </code>
/// <br/>
/// This tells the code generation for <see cref="NetworkVariableSerialization{T}"/> to generate
/// serialized code for <b>TTypeTwo</b> (generic parameter 1).
/// <br/>
/// <br/>
/// Note that this is primarily intended to support subtypes of <see cref="NetworkVariableBase"/>,
/// and as such, the type resolution is done by examining fields of <see cref="NetworkBehaviour"/>
/// subclasses. If your type is not used in a <see cref="NetworkBehaviour"/>, the codegen will
/// not find the types, even with this attribute.
/// <br/>
/// <br/>
/// This attribute is properly inherited by subclasses. For example:
/// <br/>
/// <code>
/// [SerializesGenericParameter(0)]
/// public class MyClass&lt;T&gt;
/// {
/// }
/// <br/>
/// public class MySubclass1 : MyClass&lt;Foo&gt;
/// {
/// }
/// <br/>
/// public class MySubclass2&lt;T&gt; : MyClass&lt;T&gt;
/// {
/// }
/// <br/>
/// [SerializesGenericParameter(1)]
/// public class MySubclass3&lt;TTypeOne, TTypeTwo&gt; : MyClass&lt;TTypeOne&gt;
/// {
/// }
/// <br/>
/// public class MyBehaviour : NetworkBehaviour
/// {
/// public MySubclass1 TheValue;
/// public MySubclass2&lt;Bar&gt; TheValue;
/// public MySubclass3&lt;Baz, Qux&gt; TheValue;
/// }
/// </code>
/// <br/>
/// The above code will trigger generation of serialization code for <b>Foo</b> (passed directly to the
/// base class), <b>Bar</b> (passed indirectly to the base class), <b>Baz</b> (passed indirectly to the base class),
/// and <b>Qux</b> (marked as serializable in the subclass).
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class GenerateSerializationForGenericParameterAttribute : Attribute
{
internal int ParameterIndex;
public GenerateSerializationForGenericParameterAttribute(int parameterIndex)
{
ParameterIndex = parameterIndex;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 18cdaa9c2f6446279b0c5948fcd34eec
timeCreated: 1694029524

View File

@@ -0,0 +1,26 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Specifies a specific type that needs serialization to be generated by codegen.
/// This is only needed in special circumstances where manual serialization is being done.
/// If you are making a generic network variable-style class, use <see cref="GenerateSerializationForGenericParameterAttribute"/>.
/// <br />
/// <br />
/// This attribute can be attached to any class or method anywhere in the codebase and
/// will trigger codegen to generate serialization code for the provided type. It only needs
/// to be included once type per codebase, but including it multiple times for the same type
/// is safe.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method, AllowMultiple = true)]
public class GenerateSerializationForTypeAttribute : Attribute
{
internal Type Type;
public GenerateSerializationForTypeAttribute(Type type)
{
Type = type;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1bd80306706f4054b9ba514a72076df5
timeCreated: 1694103021

View File

@@ -1,4 +1,7 @@
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Unity.Netcode
{
@@ -13,5 +16,24 @@ namespace Unity.Netcode
{
return __network_message_types;
}
#if UNITY_EDITOR
[InitializeOnLoadMethod]
public static void NotifyOnPlayStateChange()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
public static void OnPlayModeStateChanged(PlayModeStateChange change)
{
if (change == PlayModeStateChange.ExitingPlayMode)
{
// Clear out the network message types, because ILPP-generated RuntimeInitializeOnLoad code will
// run again and add more messages to it.
__network_message_types.Clear();
}
}
#endif
}
}

View File

@@ -60,6 +60,8 @@ namespace Unity.Netcode
}
}
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
}
}

View File

@@ -37,6 +37,9 @@ namespace Unity.Netcode
BytePacker.WriteValueBitPacked(writer, NetworkTick);
uint sceneObjectCount = 0;
// When SpawnedObjectsList is not null then scene management is disabled. Provide a list of
// all observed and spawned NetworkObjects that the approved client needs to synchronize.
if (SpawnedObjectsList != null)
{
var pos = writer.Position;
@@ -45,7 +48,7 @@ namespace Unity.Netcode
// Serialize NetworkVariable data
foreach (var sobj in SpawnedObjectsList)
{
if (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))
if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId)))
{
sobj.Observers.Add(OwnerClientId);
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId);

View File

@@ -34,7 +34,7 @@ namespace Unity.Netcode
return false;
}
if (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId))
if (!NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()].ContainsKey(metadata.NetworkRpcMethodId))
{
return false;
}
@@ -42,7 +42,7 @@ namespace Unity.Netcode
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
{
networkManager.NetworkMetrics.TrackRpcReceived(
context.SenderId,
@@ -67,7 +67,7 @@ namespace Unity.Netcode
try
{
NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
}
catch (Exception ex)
{
@@ -75,7 +75,7 @@ namespace Unity.Netcode
if (networkManager.LogLevel == LogLevel.Developer)
{
Debug.Log($"RPC Table Contents");
foreach (var entry in NetworkManager.__rpc_func_table)
foreach (var entry in NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()])
{
Debug.Log($"{entry.Key} | {entry.Value.Method.Name}");
}

View File

@@ -99,6 +99,8 @@ namespace Unity.Netcode
public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize;
public int FragmentedMessageMaxSize = int.MaxValue;
public Dictionary<ulong, int> PeerMTUSizes = new Dictionary<ulong, int>();
internal struct MessageWithHandler
{
public Type MessageType;
@@ -497,6 +499,7 @@ namespace Unity.Netcode
m_SendQueues.Remove(clientId);
m_PerClientMessageVersions.Remove(clientId);
PeerMTUSizes.Remove(clientId);
}
internal void CleanupDisconnectedClients()
@@ -678,6 +681,21 @@ namespace Unity.Netcode
continue;
}
var startSize = NonFragmentedMessageMaxSize;
if (delivery != NetworkDelivery.ReliableFragmentedSequenced)
{
if (PeerMTUSizes.TryGetValue(clientId, out var clientMaxSize))
{
maxSize = clientMaxSize;
}
startSize = maxSize;
if (tmpSerializer.Position >= maxSize)
{
Debug.LogError($"MTU size for {clientId} is too small to contain a message of type {typeof(TMessageType).FullName}");
continue;
}
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery);
@@ -686,7 +704,7 @@ namespace Unity.Netcode
var sendQueueItem = m_SendQueues[clientId];
if (sendQueueItem.Length == 0)
{
sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize));
sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize));
sendQueueItem.ElementAt(0).Writer.Seek(sizeof(NetworkBatchHeader));
}
else
@@ -694,7 +712,7 @@ namespace Unity.Netcode
ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
if (lastQueueItem.NetworkDelivery != delivery || lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position < tmpSerializer.Length + headerSerializer.Length)
{
sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize));
sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize));
sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(NetworkBatchHeader));
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
@@ -9,6 +8,7 @@ namespace Unity.Netcode
/// Event based NetworkVariable container for syncing Lists
/// </summary>
/// <typeparam name="T">The type for the list</typeparam>
[GenerateSerializationForGenericParameter(0)]
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
{
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
@@ -68,14 +68,7 @@ namespace Unity.Netcode
internal void MarkNetworkObjectDirty()
{
if (m_NetworkBehaviour == null)
{
Debug.LogWarning($"NetworkList is written to, but doesn't know its NetworkBehaviour yet. " +
"Are you modifying a NetworkList before the NetworkObject is spawned?");
return;
}
m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
MarkNetworkBehaviourDirty();
}
/// <inheritdoc />

View File

@@ -8,6 +8,7 @@ namespace Unity.Netcode
/// </summary>
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
[Serializable]
[GenerateSerializationForGenericParameter(0)]
public class NetworkVariable<T> : NetworkVariableBase
{
/// <summary>
@@ -146,8 +147,11 @@ namespace Unity.Netcode
// Therefore, we set the m_PreviousValue field to a duplicate of the current
// field, so that our next dirty check is made against the current "not dirty"
// value.
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Serializer.Duplicate(m_InternalValue, ref m_PreviousValue);
if (!m_HasPreviousValue || !NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_PreviousValue))
{
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
}
}
/// <summary>

View File

@@ -88,17 +88,22 @@ namespace Unity.Netcode
if (m_IsDirty)
{
if (m_NetworkBehaviour == null)
{
Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " +
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
return;
}
m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
MarkNetworkBehaviourDirty();
}
}
protected void MarkNetworkBehaviourDirty()
{
if (m_NetworkBehaviour == null)
{
Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " +
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
return;
}
m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
}
/// <summary>
/// Resets the dirty state and marks the variable as synced / clean
/// </summary>

View File

@@ -514,7 +514,7 @@ namespace Unity.Netcode
public void Duplicate(in T value, ref T duplicatedValue)
{
using var writer = new FastBufferWriter(256, Allocator.Temp);
using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue);
var refValue = value;
Write(writer, ref refValue);
@@ -580,11 +580,16 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam>
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
{
private void ThrowArgumentError()
{
throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable<T>)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
}
public void Write(FastBufferWriter writer, ref T value)
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
{
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
ThrowArgumentError();
}
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
}
@@ -592,7 +597,7 @@ namespace Unity.Netcode
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
{
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
ThrowArgumentError();
}
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
}
@@ -606,7 +611,7 @@ namespace Unity.Netcode
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
{
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
ThrowArgumentError();
}
UserNetworkVariableSerialization<T>.DuplicateValue(value, ref duplicatedValue);
}
@@ -841,8 +846,95 @@ namespace Unity.Netcode
{
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
internal delegate bool EqualsDelegate(ref T a, ref T b);
internal static EqualsDelegate AreEqual;
/// <summary>
/// A callback to check if two values are equal.
/// </summary>
public delegate bool EqualsDelegate(ref T a, ref T b);
/// <summary>
/// Uses the most efficient mechanism for a given type to determine if two values are equal.
/// For types that implement <see cref="IEquatable{T}"/>, it will call the Equals() method.
/// For unmanaged types, it will do a bytewise memory comparison.
/// For other types, it will call the == operator.
/// <br/>
/// <br/>
/// Note: If you are using this in a custom generic class, please make sure your class is
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
/// initialize the serialization mechanisms correctly. If your class is NOT
/// generic, it is better to check their equality yourself.
/// </summary>
public static EqualsDelegate AreEqual { get; internal set; }
/// <summary>
/// Serialize a value using the best-known serialization method for a generic value.
/// Will reliably serialize any value that is passed to it correctly with no boxing.
/// <br/>
/// <br/>
/// Note: If you are using this in a custom generic class, please make sure your class is
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
/// initialize the serialization mechanisms correctly. If your class is NOT
/// generic, it is better to use FastBufferWriter directly.
/// <br/>
/// <br/>
/// If the codegen is unable to determine a serializer for a type,
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.WriteValue"/> is called, which, by default,
/// will throw an exception, unless you have assigned a user serialization callback to it at runtime.
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
public static void Write(FastBufferWriter writer, ref T value)
{
Serializer.Write(writer, ref value);
}
/// <summary>
/// Deserialize a value using the best-known serialization method for a generic value.
/// Will reliably deserialize any value that is passed to it correctly with no boxing.
/// For types whose deserialization can be determined by codegen (which is most types),
/// GC will only be incurred if the type is a managed type and the ref value passed in is `null`,
/// in which case a new value is created; otherwise, it will be deserialized in-place.
/// <br/>
/// <br/>
/// Note: If you are using this in a custom generic class, please make sure your class is
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
/// initialize the serialization mechanisms correctly. If your class is NOT
/// generic, it is better to use FastBufferReader directly.
/// <br/>
/// <br/>
/// If the codegen is unable to determine a serializer for a type,
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.ReadValue"/> is called, which, by default,
/// will throw an exception, unless you have assigned a user deserialization callback to it at runtime.
/// </summary>
/// <param name="reader"></param>
/// <param name="value"></param>
public static void Read(FastBufferReader reader, ref T value)
{
Serializer.Read(reader, ref value);
}
/// <summary>
/// Duplicates a value using the most efficient means of creating a complete copy.
/// For most types this is a simple assignment or memcpy.
/// For managed types, this is will serialize and then deserialize the value to ensure
/// a correct copy.
/// <br/>
/// <br/>
/// Note: If you are using this in a custom generic class, please make sure your class is
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
/// initialize the serialization mechanisms correctly. If your class is NOT
/// generic, it is better to duplicate it directly.
/// <br/>
/// <br/>
/// If the codegen is unable to determine a serializer for a type,
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.DuplicateValue"/> is called, which, by default,
/// will throw an exception, unless you have assigned a user duplication callback to it at runtime.
/// </summary>
/// <param name="value"></param>
/// <param name="duplicatedValue"></param>
public static void Duplicate(in T value, ref T duplicatedValue)
{
Serializer.Duplicate(value, ref duplicatedValue);
}
// Compares two values of the same unmanaged type by underlying memory
// Ignoring any overridden value checks
@@ -1001,15 +1093,21 @@ namespace Unity.Netcode
{
return a == b;
}
}
internal static void Write(FastBufferWriter writer, ref T value)
// RuntimeAccessModifiersILPP will make this `public`
// This is just pass-through to NetworkVariableSerialization<T> but is here becaues I could not get ILPP
// to generate code that would successfully call Type<T>.Method(T), but it has no problem calling Type.Method<T>(T)
internal class RpcFallbackSerialization
{
public static void Write<T>(FastBufferWriter writer, ref T value)
{
Serializer.Write(writer, ref value);
NetworkVariableSerialization<T>.Write(writer, ref value);
}
internal static void Read(FastBufferReader reader, ref T value)
public static void Read<T>(FastBufferReader reader, ref T value)
{
Serializer.Read(reader, ref value);
NetworkVariableSerialization<T>.Read(reader, ref value);
}
}
}

View File

@@ -740,6 +740,14 @@ namespace Unity.Netcode
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
DontDestroyOnLoadScene = networkManager.gameObject.scene;
// Since the server tracks loaded scenes, we need to add the currently active scene
// to the list of scenes that can be unloaded.
if (networkManager.IsServer)
{
var activeScene = SceneManager.GetActiveScene();
ScenesLoaded.Add(activeScene.handle, activeScene);
}
// Add to the server to client scene handle table
UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
}

View File

@@ -257,6 +257,7 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is not spawned");
}
var previous = networkObject.OwnerClientId;
// Assign the new owner
networkObject.OwnerClientId = clientId;
@@ -286,6 +287,12 @@ namespace Unity.Netcode
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
// After we have sent the change ownership message to all client observers, invoke the ownership changed notification.
/// !!Important!!
/// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership
/// change can be sent from NetworkBehaviours that override the <see cref="NetworkBehaviour.OnOwnershipChanged"></see>
networkObject.InvokeOwnershipChanged(previous, clientId);
}
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)

View File

@@ -242,7 +242,7 @@ namespace Unity.Netcode.Transports.UTP
/// Fill the given <see cref="DataStreamWriter"/> with as many bytes from the queue as
/// possible, disregarding message boundaries.
/// </summary>
///<remarks>
/// <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
@@ -252,15 +252,17 @@ namespace Unity.Netcode.Transports.UTP
/// this could lead to reading messages from a corrupted queue.
/// </remarks>
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
/// <param name="maxBytes">Max number of bytes to copy (0 means writer capacity).</param>
/// <returns>How many bytes were written to the writer.</returns>
public int FillWriterWithBytes(ref DataStreamWriter writer)
public int FillWriterWithBytes(ref DataStreamWriter writer, int maxBytes = 0)
{
if (!IsCreated || Length == 0)
{
return 0;
}
var copyLength = Math.Min(writer.Capacity, Length);
var maxLength = maxBytes == 0 ? writer.Capacity : Math.Min(maxBytes, writer.Capacity);
var copyLength = Math.Min(maxLength, Length);
unsafe
{

View File

@@ -727,6 +727,7 @@ namespace Unity.Netcode.Transports.UTP
public SendTarget Target;
public BatchedSendQueue Queue;
public NetworkPipeline ReliablePipeline;
public int MTU;
public void Execute()
{
@@ -749,7 +750,7 @@ namespace Unity.Netcode.Transports.UTP
// in the stream (the send queue does that automatically) we are sure they'll be
// reassembled properly at the other end. This allows us to lift the limit of ~44KB
// on reliable payloads (because of the reliable window size).
var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer) : Queue.FillWriterWithMessages(ref writer);
var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer, MTU) : Queue.FillWriterWithMessages(ref writer);
result = Driver.EndSend(writer);
if (result == written)
@@ -783,12 +784,21 @@ namespace Unity.Netcode.Transports.UTP
{
return;
}
var mtu = 0;
if (NetworkManager)
{
var ngoClientId = NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId);
mtu = NetworkManager.GetPeerMTU(ngoClientId);
}
new SendBatchedMessagesJob
{
Driver = m_Driver.ToConcurrent(),
Target = sendTarget,
Queue = queue,
ReliablePipeline = m_ReliableSequencedPipeline
ReliablePipeline = m_ReliableSequencedPipeline,
MTU = mtu,
}.Run();
}
@@ -1560,6 +1570,21 @@ namespace Unity.Netcode.Transports.UTP
}
#endif
#if UTP_TRANSPORT_2_1_ABOVE
if (m_ProtocolType == ProtocolType.RelayUnityTransport)
{
if (m_UseWebSockets && m_RelayServerData.IsWebSocket == 0)
{
Debug.LogError("Transport is configured to use WebSockets, but Relay server data isn't. Be sure to use \"wss\" as the connection type when creating the server data (instead of \"dtls\" or \"udp\").");
}
if (!m_UseWebSockets && m_RelayServerData.IsWebSocket != 0)
{
Debug.LogError("Relay server data indicates usage of WebSockets, but \"Use WebSockets\" checkbox isn't checked under \"Unity Transport\" component.");
}
}
#endif
#if UTP_TRANSPORT_2_0_ABOVE
if (m_UseWebSockets)
{
@@ -1567,7 +1592,7 @@ namespace Unity.Netcode.Transports.UTP
}
else
{
#if UNITY_WEBGL
#if UNITY_WEBGL && !UNITY_EDITOR
Debug.LogWarning($"WebSockets were used even though they're not selected in NetworkManager. You should check {nameof(UseWebSockets)}', on the Unity Transport component, to silence this warning.");
driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings);
#else

View File

@@ -36,6 +36,11 @@
"name": "com.unity.transport",
"expression": "2.0.0-exp",
"define": "UTP_TRANSPORT_2_0_ABOVE"
},
{
"name": "com.unity.transport",
"expression": "2.1.0",
"define": "UTP_TRANSPORT_2_1_ABOVE"
}
]
}