Compare commits
2 Commits
1.0.0-pre.
...
1.0.0-pre.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4818405514 | ||
|
|
36d07fad5e |
47
CHANGELOG.md
47
CHANGELOG.md
@@ -6,6 +6,53 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||
|
||||
## [1.0.0-pre.5] - 2022-01-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added `PreviousValue` in `NetworkListEvent`, when `Value` has changed (#1528)
|
||||
|
||||
### Changed
|
||||
|
||||
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
|
||||
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
|
||||
- Fixed The ClientNetworkTransform sample script to allow for owner changes at runtime. (#1606)
|
||||
|
||||
## [1.0.0-pre.4] - 2021-01-04
|
||||
|
||||
### Added
|
||||
|
||||
- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `com.unity.modules.ai` package dependency (#1565)
|
||||
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
|
||||
|
||||
### Fixed
|
||||
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
|
||||
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
|
||||
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
|
||||
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
|
||||
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
|
||||
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
|
||||
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
|
||||
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
|
||||
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
|
||||
- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
|
||||
- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)
|
||||
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
|
||||
|
||||
### Changed
|
||||
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
|
||||
- Updated com.unity.collections to 1.1.0 (#1451)
|
||||
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
|
||||
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)
|
||||
|
||||
## [1.0.0-pre.3] - 2021-10-22
|
||||
|
||||
### Added
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Unity.Netcode.Components
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (NetworkManager.IsListening)
|
||||
if (IsSpawned)
|
||||
{
|
||||
if (HasAuthority != m_IsAuthority)
|
||||
{
|
||||
@@ -74,7 +74,6 @@ namespace Unity.Netcode.Components
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
m_IsAuthority = false;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,7 +807,7 @@ namespace Unity.Netcode.Components
|
||||
// conditional to users only making transform update changes in FixedUpdate.
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (!NetworkObject.IsSpawned)
|
||||
if (!IsSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -264,6 +264,28 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddWarning(this List<DiagnosticMessage> diagnostics, string message)
|
||||
{
|
||||
diagnostics.AddWarning((SequencePoint)null, message);
|
||||
}
|
||||
|
||||
public static void AddWarning(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDefinition, string message)
|
||||
{
|
||||
diagnostics.AddWarning(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message);
|
||||
}
|
||||
|
||||
public static void AddWarning(this List<DiagnosticMessage> diagnostics, SequencePoint sequencePoint, string message)
|
||||
{
|
||||
diagnostics.Add(new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Warning,
|
||||
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""),
|
||||
Line = sequencePoint?.StartLine ?? 0,
|
||||
Column = sequencePoint?.StartColumn ?? 0,
|
||||
MessageData = $" - {message}"
|
||||
});
|
||||
}
|
||||
|
||||
public static void RemoveRecursiveReferences(this ModuleDefinition moduleDefinition)
|
||||
{
|
||||
// Weird behavior from Cecil: When importing a reference to a specific implementation of a generic
|
||||
|
||||
167
Editor/CodeGen/INetworkSerializableILPP.cs
Normal file
167
Editor/CodeGen/INetworkSerializableILPP.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
|
||||
internal sealed class INetworkSerializableILPP : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!WillProcess(compiledAssembly))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver);
|
||||
if (assemblyDefinition == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ImportReferences(mainModule))
|
||||
{
|
||||
var types = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && t.Resolve().IsValueType)
|
||||
.ToList();
|
||||
// process `INetworkMessage` types
|
||||
if (types.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
CreateModuleInitializer(assemblyDefinition, types);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot get main module from assembly definition: {compiledAssembly.Name}");
|
||||
}
|
||||
|
||||
mainModule.RemoveRecursiveReferences();
|
||||
|
||||
// write
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
|
||||
var writerParameters = new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
SymbolStream = pdb,
|
||||
WriteSymbols = true
|
||||
};
|
||||
|
||||
assemblyDefinition.Write(pe, writerParameters);
|
||||
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private MethodReference m_InitializeDelegates_MethodRef;
|
||||
|
||||
private const string k_InitializeMethodName = nameof(NetworkVariableHelper.InitializeDelegates);
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
|
||||
var helperType = typeof(NetworkVariableHelper);
|
||||
foreach (var methodInfo in helperType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
{
|
||||
case k_InitializeMethodName:
|
||||
m_InitializeDelegates_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
if (staticCtorMethodDef == null)
|
||||
{
|
||||
staticCtorMethodDef = new MethodDefinition(
|
||||
".cctor", // Static Constructor (constant-constructor)
|
||||
MethodAttributes.HideBySig |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.RTSpecialName |
|
||||
MethodAttributes.Static,
|
||||
typeDefinition.Module.TypeSystem.Void);
|
||||
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
typeDefinition.Methods.Add(staticCtorMethodDef);
|
||||
}
|
||||
|
||||
return staticCtorMethodDef;
|
||||
}
|
||||
|
||||
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
|
||||
// message types in the assembly with MessagingSystem.
|
||||
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
|
||||
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
|
||||
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
|
||||
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkSerializableTypes)
|
||||
{
|
||||
foreach (var typeDefinition in assembly.MainModule.Types)
|
||||
{
|
||||
if (typeDefinition.FullName == "<Module>")
|
||||
{
|
||||
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
|
||||
|
||||
var processor = staticCtorMethodDef.Body.GetILProcessor();
|
||||
|
||||
var instructions = new List<Instruction>();
|
||||
|
||||
foreach (var type in networkSerializableTypes)
|
||||
{
|
||||
var method = new GenericInstanceMethod(m_InitializeDelegates_MethodRef);
|
||||
method.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, method));
|
||||
}
|
||||
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/CodeGen/INetworkSerializableILPP.cs.meta
Normal file
3
Editor/CodeGen/INetworkSerializableILPP.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64a0c1e708fa46a389d64e7b4708e6c7
|
||||
timeCreated: 1635535237
|
||||
@@ -312,7 +312,11 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
assemblies.Add(m_MainModule.Assembly);
|
||||
foreach (var reference in m_MainModule.AssemblyReferences)
|
||||
{
|
||||
assemblies.Add(m_AssemblyResolver.Resolve(reference));
|
||||
var assembly = m_AssemblyResolver.Resolve(reference);
|
||||
if (assembly != null)
|
||||
{
|
||||
assemblies.Add(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
var extensionConstructor =
|
||||
@@ -575,15 +579,23 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
if (method.GenericParameters[0].HasConstraints)
|
||||
{
|
||||
var meetsConstraints = true;
|
||||
foreach (var constraint in method.GenericParameters[0].Constraints)
|
||||
{
|
||||
var resolvedConstraint = constraint.Resolve();
|
||||
|
||||
if (
|
||||
(resolvedConstraint.IsInterface &&
|
||||
checkType.HasInterface(resolvedConstraint.FullName))
|
||||
!checkType.HasInterface(resolvedConstraint.FullName))
|
||||
|| (resolvedConstraint.IsClass &&
|
||||
checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)))
|
||||
!checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))
|
||||
|| (resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
|
||||
{
|
||||
meetsConstraints = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (meetsConstraints)
|
||||
{
|
||||
var instanceMethod = new GenericInstanceMethod(method);
|
||||
instanceMethod.GenericArguments.Add(checkType);
|
||||
@@ -593,7 +605,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -950,17 +961,23 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
// writer.WriteValueSafe(param) for value types, OR
|
||||
// writer.WriteValueSafe(param, -1, 0) for arrays of value types, OR
|
||||
// writer.WriteValueSafe(param, false) for strings
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
|
||||
var method = methodRef.Resolve();
|
||||
var checkParameter = method.Parameters[0];
|
||||
var isExtensionMethod = false;
|
||||
if (checkParameter.ParameterType.Resolve() ==
|
||||
m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve())
|
||||
if (methodRef.Resolve().DeclaringType != m_FastBufferWriter_TypeRef.Resolve())
|
||||
{
|
||||
isExtensionMethod = true;
|
||||
checkParameter = method.Parameters[1];
|
||||
}
|
||||
if (checkParameter.IsIn)
|
||||
if (!isExtensionMethod || method.Parameters[0].ParameterType.IsByReference)
|
||||
{
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
|
||||
}
|
||||
else
|
||||
{
|
||||
instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx));
|
||||
}
|
||||
if (checkParameter.IsIn || checkParameter.IsOut || checkParameter.ParameterType.IsByReference)
|
||||
{
|
||||
instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1));
|
||||
}
|
||||
@@ -1271,7 +1288,18 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
if (foundMethodRef)
|
||||
{
|
||||
// reader.ReadValueSafe(out localVar);
|
||||
|
||||
var checkParameter = methodRef.Resolve().Parameters[0];
|
||||
|
||||
var isExtensionMethod = methodRef.Resolve().DeclaringType != m_FastBufferReader_TypeRef.Resolve();
|
||||
if (!isExtensionMethod || checkParameter.ParameterType.IsByReference)
|
||||
{
|
||||
processor.Emit(OpCodes.Ldarga, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
processor.Emit(OpCodes.Ldarg, 1);
|
||||
}
|
||||
processor.Emit(OpCodes.Ldloca, localIndex);
|
||||
if (paramType == typeSystem.String)
|
||||
{
|
||||
|
||||
@@ -52,6 +52,9 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
case nameof(NetworkBehaviour):
|
||||
ProcessNetworkBehaviour(typeDefinition);
|
||||
break;
|
||||
case nameof(NetworkVariableHelper):
|
||||
ProcessNetworkVariableHelper(typeDefinition);
|
||||
break;
|
||||
case nameof(__RpcParams):
|
||||
typeDefinition.IsPublic = true;
|
||||
break;
|
||||
@@ -100,6 +103,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkVariableHelper(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var methodDefinition in typeDefinition.Methods)
|
||||
{
|
||||
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegates))
|
||||
{
|
||||
methodDefinition.IsPublic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var nestedType in typeDefinition.NestedTypes)
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Unity.Netcode.Editor
|
||||
private static GUIStyle s_HelpBoxStyle;
|
||||
|
||||
// Properties
|
||||
private SerializedProperty m_DontDestroyOnLoadProperty;
|
||||
private SerializedProperty m_RunInBackgroundProperty;
|
||||
private SerializedProperty m_LogLevelProperty;
|
||||
|
||||
@@ -85,7 +84,6 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkManager = (NetworkManager)target;
|
||||
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
@@ -112,7 +110,6 @@ namespace Unity.Netcode.Editor
|
||||
private void CheckNullProperties()
|
||||
{
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
@@ -223,7 +220,6 @@ namespace Unity.Netcode.Editor
|
||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty);
|
||||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
||||
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
||||
EditorGUILayout.Space();
|
||||
@@ -363,7 +359,7 @@ namespace Unity.Netcode.Editor
|
||||
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
|
||||
const string openDocsButtonText = "Open Docs";
|
||||
const string dismissButtonText = "Dismiss";
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tutorials/goldenpath_series/goldenpath_foundation_module";
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tools/install-tools";
|
||||
const string infoIconName = "console.infoicon";
|
||||
|
||||
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
|
||||
|
||||
108
Editor/NetworkManagerHelper.cs
Normal file
108
Editor/NetworkManagerHelper.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Specialized editor specific NetworkManager code
|
||||
/// </summary>
|
||||
public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper
|
||||
{
|
||||
internal static NetworkManagerHelper Singleton;
|
||||
|
||||
// This is primarily to handle multiInstance scenarios where more than 1 NetworkManager could exist
|
||||
private static Dictionary<NetworkManager, Transform> s_LastKnownNetworkManagerParents = new Dictionary<NetworkManager, Transform>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton instance and registers for:
|
||||
/// Hierarchy changed notification: to notify the user when they nest a NetworkManager
|
||||
/// Play mode state change notification: to capture when entering or exiting play mode (currently only exiting)
|
||||
/// </summary>
|
||||
[InitializeOnLoadMethod]
|
||||
private static void InitializeOnload()
|
||||
{
|
||||
Singleton = new NetworkManagerHelper();
|
||||
NetworkManager.NetworkManagerHelper = Singleton;
|
||||
|
||||
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
|
||||
|
||||
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged;
|
||||
}
|
||||
|
||||
private static void EditorApplication_playModeStateChanged(PlayModeStateChange playModeStateChange)
|
||||
{
|
||||
switch (playModeStateChange)
|
||||
{
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EditorApplication_hierarchyChanged()
|
||||
{
|
||||
var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>();
|
||||
foreach (var networkManager in allNetworkManagers)
|
||||
{
|
||||
networkManager.NetworkManagerCheckForParent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles notifying the user, via display dialog window, that they have nested a NetworkManager.
|
||||
/// When in edit mode it provides the option to automatically fix the issue
|
||||
/// When in play mode it just notifies the user when entering play mode as well as when the user
|
||||
/// tries to start a network session while a NetworkManager is still nested.
|
||||
/// </summary>
|
||||
public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false)
|
||||
{
|
||||
var gameObject = networkManager.gameObject;
|
||||
var transform = networkManager.transform;
|
||||
var isParented = transform.root != transform;
|
||||
|
||||
var message = NetworkManager.GenerateNestedNetworkManagerMessage(transform);
|
||||
if (s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && !ignoreNetworkManagerCache)
|
||||
{
|
||||
// If we have already notified the user, then don't notify them again
|
||||
if (s_LastKnownNetworkManagerParents[networkManager] == transform.root)
|
||||
{
|
||||
return isParented;
|
||||
}
|
||||
else // If we are no longer a child, then we can remove ourself from this list
|
||||
if (transform.root == gameObject.transform)
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Remove(networkManager);
|
||||
}
|
||||
}
|
||||
if (!EditorApplication.isUpdating && isParented)
|
||||
{
|
||||
if (!EditorApplication.isPlaying && !editorTest)
|
||||
{
|
||||
message += $"Click 'Auto-Fix' to automatically remove it from {transform.root.gameObject.name} or 'Manual-Fix' to fix it yourself in the hierarchy view.";
|
||||
if (EditorUtility.DisplayDialog("Invalid Nested NetworkManager", message, "Auto-Fix", "Manual-Fix"))
|
||||
{
|
||||
transform.parent = null;
|
||||
isParented = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(message);
|
||||
}
|
||||
|
||||
if (!s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && isParented)
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Add(networkManager, networkManager.transform.root);
|
||||
}
|
||||
}
|
||||
return isParented;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e168a2bc1a1e2642af0369780fb560c
|
||||
guid: b26b53dc28ae1b5488bbbecc3e499bbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,7 +1,7 @@
|
||||
[](https://forum.unity.com/forums/multiplayer.26/) [](https://discord.gg/FM8SE9E)
|
||||
[](https://docs-multiplayer.unity3d.com/) [](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
|
||||
Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs.unity3d.com/Packages/com.unity.transport@1.0/manual/index.html).
|
||||
Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs-multiplayer.unity3d.com/transport/1.0.0/introduction).
|
||||
|
||||
### Getting Started
|
||||
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2ef964afcae91248b2298b479ed1b53
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,77 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue with a fixed size
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the queue</typeparam>
|
||||
public sealed class FixedQueue<T>
|
||||
{
|
||||
private readonly T[] m_Queue;
|
||||
private int m_QueueCount = 0;
|
||||
private int m_QueueStart;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of enqueued objects
|
||||
/// </summary>
|
||||
public int Count => m_QueueCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new FixedQueue with a given size
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The size of the queue</param>
|
||||
public FixedQueue(int maxSize)
|
||||
{
|
||||
m_Queue = new T[maxSize];
|
||||
m_QueueStart = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an object
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <returns></returns>
|
||||
public bool Enqueue(T t)
|
||||
{
|
||||
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
|
||||
if (++m_QueueCount > m_Queue.Length)
|
||||
{
|
||||
--m_QueueCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues an object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T Dequeue()
|
||||
{
|
||||
if (--m_QueueCount == -1)
|
||||
{
|
||||
throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
|
||||
}
|
||||
|
||||
T res = m_Queue[m_QueueStart];
|
||||
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
}
|
||||
}
|
||||
@@ -141,15 +141,15 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
|
||||
/// </summary>
|
||||
public bool UseSnapshotDelta { get; } = false;
|
||||
public bool UseSnapshotDelta { get; internal set; } = false;
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
|
||||
/// </summary>
|
||||
public bool UseSnapshotSpawn { get; } = false;
|
||||
public bool UseSnapshotSpawn { get; internal set; } = false;
|
||||
/// <summary>
|
||||
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
|
||||
/// </summary>
|
||||
public int SnapshotMaxSpawnUsage { get; } = 1200;
|
||||
public int SnapshotMaxSpawnUsage { get; } = 1000;
|
||||
|
||||
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
|
||||
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
|
||||
@@ -224,7 +224,7 @@ namespace Unity.Netcode
|
||||
return m_ConfigHash.Value;
|
||||
}
|
||||
|
||||
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
|
||||
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, int.MaxValue);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteValueSafe(ProtocolVersion);
|
||||
|
||||
@@ -5,6 +5,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal static class NetworkConstants
|
||||
{
|
||||
internal const string PROTOCOL_VERSION = "14.0.0";
|
||||
internal const string PROTOCOL_VERSION = "15.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,8 @@ namespace Unity.Netcode
|
||||
SystemOwner = NetworkManager,
|
||||
// header information isn't valid since it's not a real message.
|
||||
// Passing false to canDefer prevents it being accessed.
|
||||
Header = new MessageHeader()
|
||||
Header = new MessageHeader(),
|
||||
SerializedHeaderSize = 0,
|
||||
};
|
||||
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
|
||||
rpcMessageSize = tempBuffer.Length;
|
||||
@@ -188,7 +189,8 @@ namespace Unity.Netcode
|
||||
SystemOwner = NetworkManager,
|
||||
// header information isn't valid since it's not a real message.
|
||||
// Passing false to canDefer prevents it being accessed.
|
||||
Header = new MessageHeader()
|
||||
Header = new MessageHeader(),
|
||||
SerializedHeaderSize = 0,
|
||||
};
|
||||
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
|
||||
messageSize = tempBuffer.Length;
|
||||
@@ -282,10 +284,14 @@ namespace Unity.Netcode
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
|
||||
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
if (m_NetworkObject == null || NetworkManager.Singleton == null ||
|
||||
(NetworkManager.Singleton != null && !NetworkManager.Singleton.ShutdownInProgress))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel < LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
|
||||
}
|
||||
}
|
||||
|
||||
return m_NetworkObject;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
private bool m_ShuttingDown;
|
||||
private bool m_StopProcessingMessages;
|
||||
|
||||
private class NetworkManagerHooks : INetworkHooks
|
||||
{
|
||||
private NetworkManager m_NetworkManager;
|
||||
@@ -116,7 +119,7 @@ namespace Unity.Netcode
|
||||
|
||||
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
|
||||
{
|
||||
return true;
|
||||
return !m_NetworkManager.m_StopProcessingMessages;
|
||||
}
|
||||
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
||||
@@ -134,7 +137,7 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !m_NetworkManager.m_StopProcessingMessages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,11 +197,6 @@ namespace Unity.Netcode
|
||||
|
||||
public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the NetworkManager should be marked as DontDestroyOnLoad
|
||||
/// </summary>
|
||||
[HideInInspector] public bool DontDestroy = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the application should be set to run in background
|
||||
/// </summary>
|
||||
@@ -333,6 +331,9 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public bool IsConnectedClient { get; internal set; }
|
||||
|
||||
|
||||
public bool ShutdownInProgress { get { return m_ShuttingDown; } }
|
||||
|
||||
/// <summary>
|
||||
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
|
||||
/// </summary>
|
||||
@@ -345,8 +346,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public event Action<ulong> OnClientDisconnectCallback = null;
|
||||
|
||||
internal void InvokeOnClientDisconnectCallback(ulong clientId) => OnClientDisconnectCallback?.Invoke(clientId);
|
||||
|
||||
/// <summary>
|
||||
/// The callback to invoke once the server is ready
|
||||
/// </summary>
|
||||
@@ -486,6 +485,13 @@ namespace Unity.Netcode
|
||||
|
||||
private void Initialize(bool server)
|
||||
{
|
||||
// Don't allow the user to start a network session if the NetworkManager is
|
||||
// still parented under another GameObject
|
||||
if (NetworkManagerCheckForParent(true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo(nameof(Initialize));
|
||||
@@ -556,8 +562,6 @@ namespace Unity.Netcode
|
||||
SnapshotSystem = null;
|
||||
}
|
||||
|
||||
SnapshotSystem = new SnapshotSystem(this);
|
||||
|
||||
if (server)
|
||||
{
|
||||
NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
|
||||
@@ -570,6 +574,8 @@ namespace Unity.Netcode
|
||||
NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0);
|
||||
NetworkTickSystem.Tick += OnNetworkManagerTick;
|
||||
|
||||
SnapshotSystem = new SnapshotSystem(this, NetworkConfig, NetworkTickSystem);
|
||||
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
|
||||
|
||||
// This is used to remove entries not needed or invalid
|
||||
@@ -937,11 +943,6 @@ namespace Unity.Netcode
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (DontDestroy)
|
||||
{
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
if (RunInBackground)
|
||||
{
|
||||
Application.runInBackground = true;
|
||||
@@ -951,6 +952,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
SetSingleton();
|
||||
}
|
||||
|
||||
if (!NetworkManagerCheckForParent())
|
||||
{
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
@@ -958,6 +964,48 @@ namespace Unity.Netcode
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject
|
||||
/// </summary>
|
||||
private void OnTransformParentChanged()
|
||||
{
|
||||
NetworkManagerCheckForParent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the NetworkManager's GameObject is parented under another GameObject and
|
||||
/// notifies the user that this is not allowed for the NetworkManager.
|
||||
/// </summary>
|
||||
internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = false)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var isParented = NetworkManagerHelper.NotifyUserOfNestedNetworkManager(this, ignoreNetworkManagerCache);
|
||||
#else
|
||||
var isParented = transform.root != transform;
|
||||
if (isParented)
|
||||
{
|
||||
throw new Exception(GenerateNestedNetworkManagerMessage(transform));
|
||||
}
|
||||
#endif
|
||||
return isParented;
|
||||
}
|
||||
|
||||
static internal string GenerateNestedNetworkManagerMessage(Transform transform)
|
||||
{
|
||||
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static internal INetworkManagerHelper NetworkManagerHelper;
|
||||
/// <summary>
|
||||
/// Interface for NetworkManagerHelper
|
||||
/// </summary>
|
||||
internal interface INetworkManagerHelper
|
||||
{
|
||||
bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
|
||||
private void OnSceneUnloaded(Scene scene)
|
||||
{
|
||||
@@ -976,7 +1024,7 @@ namespace Unity.Netcode
|
||||
// Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
|
||||
private void OnDestroy()
|
||||
{
|
||||
Shutdown();
|
||||
ShutdownInternal();
|
||||
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
|
||||
|
||||
@@ -996,13 +1044,30 @@ namespace Unity.Netcode
|
||||
/// Globally shuts down the library.
|
||||
/// Disconnects clients if connected and stops server if running.
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
/// <param name="discardMessageQueue">
|
||||
/// If false, any messages that are currently in the incoming queue will be handled,
|
||||
/// and any messages in the outgoing queue will be sent, before the shutdown is processed.
|
||||
/// If true, NetworkManager will shut down immediately, and any unprocessed or unsent messages
|
||||
/// will be discarded.
|
||||
/// </param>
|
||||
public void Shutdown(bool discardMessageQueue = false)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo(nameof(Shutdown));
|
||||
}
|
||||
|
||||
m_ShuttingDown = true;
|
||||
m_StopProcessingMessages = discardMessageQueue;
|
||||
}
|
||||
|
||||
internal void ShutdownInternal()
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo(nameof(ShutdownInternal));
|
||||
}
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
// make sure all messages are flushed before transport disconnect clients
|
||||
@@ -1075,11 +1140,15 @@ namespace Unity.Netcode
|
||||
MessagingSystem = null;
|
||||
}
|
||||
|
||||
if (NetworkConfig?.NetworkTransport != null)
|
||||
{
|
||||
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
|
||||
}
|
||||
|
||||
if (SpawnManager != null)
|
||||
{
|
||||
SpawnManager.DestroyNonSceneObjects();
|
||||
SpawnManager.CleanupAllTriggers();
|
||||
SpawnManager.DespawnAndDestroyNetworkObjects();
|
||||
SpawnManager.ServerResetShudownStateForSceneObjects();
|
||||
|
||||
SpawnManager = null;
|
||||
@@ -1114,6 +1183,8 @@ namespace Unity.Netcode
|
||||
m_TransportIdToClientIdMap.Clear();
|
||||
|
||||
IsListening = false;
|
||||
m_ShuttingDown = false;
|
||||
m_StopProcessingMessages = false;
|
||||
}
|
||||
|
||||
// INetworkUpdateSystem
|
||||
@@ -1167,6 +1238,11 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ShuttingDown && m_StopProcessingMessages)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only update RTT here, server time is updated by time sync messages
|
||||
var reset = NetworkTimeSystem.Advance(Time.deltaTime);
|
||||
if (reset)
|
||||
@@ -1182,10 +1258,19 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
private void OnNetworkPostLateUpdate()
|
||||
{
|
||||
|
||||
if (!m_ShuttingDown || !m_StopProcessingMessages)
|
||||
{
|
||||
MessagingSystem.ProcessSendQueues();
|
||||
NetworkMetrics.DispatchFrame();
|
||||
}
|
||||
SpawnManager.CleanupStaleTriggers();
|
||||
|
||||
if (m_ShuttingDown)
|
||||
{
|
||||
ShutdownInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1312,6 +1397,8 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
clientId = TransportIdToClientId(clientId);
|
||||
|
||||
OnClientDisconnectCallback?.Invoke(clientId);
|
||||
|
||||
m_TransportIdToClientIdMap.Remove(transportId);
|
||||
m_ClientIdToTransportIdMap.Remove(clientId);
|
||||
|
||||
@@ -1328,9 +1415,6 @@ namespace Unity.Netcode
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
OnClientDisconnectCallback?.Invoke(clientId);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_TransportDisconnect.End();
|
||||
#endif
|
||||
@@ -1596,6 +1680,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
else // Server just adds itself as an observer to all spawned NetworkObjects
|
||||
{
|
||||
LocalClient = client;
|
||||
SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
|
||||
InvokeOnClientConnectedCallback(ownerClientId);
|
||||
}
|
||||
|
||||
@@ -62,20 +62,63 @@ namespace Unity.Netcode
|
||||
internal int TimesWritten;
|
||||
}
|
||||
|
||||
internal class ClientData
|
||||
{
|
||||
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
|
||||
{
|
||||
internal ulong SequenceNumber;
|
||||
internal ulong ObjectId;
|
||||
internal int Tick;
|
||||
}
|
||||
|
||||
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
|
||||
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
|
||||
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
|
||||
|
||||
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
|
||||
internal int NextDespawnIndex = 0; // same as above, but for despawns.
|
||||
|
||||
// by objectId
|
||||
// which spawns and despawns did this connection ack'ed ?
|
||||
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
|
||||
|
||||
// list of spawn and despawns commands we sent, with sequence number
|
||||
// need to manage acknowledgements
|
||||
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
|
||||
}
|
||||
|
||||
internal delegate int MockSendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId);
|
||||
internal delegate int MockSpawnObject(SnapshotSpawnCommand spawnCommand);
|
||||
internal delegate int MockDespawnObject(SnapshotDespawnCommand despawnCommand);
|
||||
|
||||
|
||||
// A table of NetworkVariables that constitutes a Snapshot.
|
||||
// Stores serialized NetworkVariables
|
||||
// todo --M1--
|
||||
// The Snapshot will change for M1b with memory management, instead of just FreeMemoryPosition, there will be data structure
|
||||
// around available buffer, etc.
|
||||
internal class Snapshot
|
||||
internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
|
||||
{
|
||||
// todo --M1-- functionality to grow these will be needed in a later milestone
|
||||
private const int k_MaxVariables = 2000;
|
||||
private int m_MaxSpawns = 100;
|
||||
private int m_MaxDespawns = 100;
|
||||
internal int SpawnsBufferCount { get; private set; } = 100;
|
||||
internal int DespawnsBufferCount { get; private set; } = 100;
|
||||
|
||||
private const int k_BufferSize = 30000;
|
||||
|
||||
private NetworkManager m_NetworkManager = default;
|
||||
|
||||
// by clientId
|
||||
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
|
||||
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
|
||||
|
||||
private bool m_UseSnapshotDelta;
|
||||
private bool m_UseSnapshotSpawn;
|
||||
private int m_SnapshotMaxSpawnUsage;
|
||||
private NetworkTickSystem m_NetworkTickSystem;
|
||||
|
||||
private int m_CurrentTick = NetworkTickSystem.NoTick;
|
||||
|
||||
internal byte[] MainBuffer = new byte[k_BufferSize]; // buffer holding a snapshot in memory
|
||||
internal byte[] RecvBuffer = new byte[k_BufferSize]; // buffer holding the received snapshot message
|
||||
|
||||
@@ -90,23 +133,17 @@ namespace Unity.Netcode
|
||||
internal SnapshotDespawnCommand[] Despawns;
|
||||
internal int NumDespawns = 0;
|
||||
|
||||
internal NetworkManager NetworkManager;
|
||||
|
||||
// indexed by ObjectId
|
||||
internal Dictionary<ulong, int> TickAppliedSpawn = new Dictionary<ulong, int>();
|
||||
internal Dictionary<ulong, int> TickAppliedDespawn = new Dictionary<ulong, int>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// Allocated a MemoryStream to be reused for this Snapshot
|
||||
/// </summary>
|
||||
internal Snapshot()
|
||||
{
|
||||
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used
|
||||
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
|
||||
Spawns = new SnapshotSpawnCommand[m_MaxSpawns];
|
||||
Despawns = new SnapshotDespawnCommand[m_MaxDespawns];
|
||||
}
|
||||
internal bool IsServer { get; set; }
|
||||
internal bool IsConnectedClient { get; set; }
|
||||
internal ulong ServerClientId { get; set; }
|
||||
internal List<ulong> ConnectedClientsId { get; } = new List<ulong>();
|
||||
internal MockSendMessage MockSendMessage { get; set; }
|
||||
internal MockSpawnObject MockSpawnObject { get; set; }
|
||||
internal MockDespawnObject MockDespawnObject { get; set; }
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
@@ -156,15 +193,15 @@ namespace Unity.Netcode
|
||||
List<ulong> clientList;
|
||||
clientList = new List<ulong>();
|
||||
|
||||
if (!NetworkManager.IsServer)
|
||||
if (!IsServer)
|
||||
{
|
||||
clientList.Add(NetworkManager.ServerClientId);
|
||||
clientList.Add(m_NetworkManager.ServerClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var clientId in NetworkManager.ConnectedClientsIds)
|
||||
foreach (var clientId in ConnectedClientsId)
|
||||
{
|
||||
if (clientId != NetworkManager.ServerClientId)
|
||||
if (clientId != m_NetworkManager.ServerClientId)
|
||||
{
|
||||
clientList.Add(clientId);
|
||||
}
|
||||
@@ -176,14 +213,14 @@ namespace Unity.Netcode
|
||||
|
||||
internal void AddSpawn(SnapshotSpawnCommand command)
|
||||
{
|
||||
if (NumSpawns >= m_MaxSpawns)
|
||||
if (NumSpawns >= SpawnsBufferCount)
|
||||
{
|
||||
Array.Resize(ref Spawns, 2 * m_MaxSpawns);
|
||||
m_MaxSpawns = m_MaxSpawns * 2;
|
||||
Array.Resize(ref Spawns, 2 * SpawnsBufferCount);
|
||||
SpawnsBufferCount = SpawnsBufferCount * 2;
|
||||
// Debug.Log($"[JEFF] spawn size is now {m_MaxSpawns}");
|
||||
}
|
||||
|
||||
if (NumSpawns < m_MaxSpawns)
|
||||
if (NumSpawns < SpawnsBufferCount)
|
||||
{
|
||||
if (command.TargetClientIds == default)
|
||||
{
|
||||
@@ -208,14 +245,14 @@ namespace Unity.Netcode
|
||||
|
||||
internal void AddDespawn(SnapshotDespawnCommand command)
|
||||
{
|
||||
if (NumDespawns >= m_MaxDespawns)
|
||||
if (NumDespawns >= DespawnsBufferCount)
|
||||
{
|
||||
Array.Resize(ref Despawns, 2 * m_MaxDespawns);
|
||||
m_MaxDespawns = m_MaxDespawns * 2;
|
||||
Array.Resize(ref Despawns, 2 * DespawnsBufferCount);
|
||||
DespawnsBufferCount = DespawnsBufferCount * 2;
|
||||
// Debug.Log($"[JEFF] despawn size is now {m_MaxDespawns}");
|
||||
}
|
||||
|
||||
if (NumDespawns < m_MaxDespawns)
|
||||
if (NumDespawns < DespawnsBufferCount)
|
||||
{
|
||||
if (command.TargetClientIds == default)
|
||||
{
|
||||
@@ -229,6 +266,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void ReduceBufferUsage()
|
||||
{
|
||||
var count = Math.Max(1, NumDespawns);
|
||||
Array.Resize(ref Despawns, count);
|
||||
DespawnsBufferCount = count;
|
||||
|
||||
count = Math.Max(1, NumSpawns);
|
||||
Array.Resize(ref Spawns, count);
|
||||
SpawnsBufferCount = count;
|
||||
}
|
||||
|
||||
internal ClientData.SentSpawn GetSpawnData(in ClientData clientData, in SnapshotSpawnCommand spawn, out SnapshotDataMessage.SpawnData data)
|
||||
{
|
||||
// remember which spawn we sent this connection with which sequence number
|
||||
@@ -417,7 +465,54 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void ReadSpawns(in SnapshotDataMessage message)
|
||||
internal void SpawnObject(SnapshotSpawnCommand spawnCommand, ulong srcClientId)
|
||||
{
|
||||
if (m_NetworkManager)
|
||||
{
|
||||
NetworkObject networkObject;
|
||||
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId)
|
||||
{
|
||||
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
|
||||
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition,
|
||||
spawnCommand.ObjectRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
|
||||
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition,
|
||||
spawnCommand.ObjectRotation);
|
||||
}
|
||||
|
||||
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId,
|
||||
true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
|
||||
//todo: discuss with tools how to report shared bytes
|
||||
m_NetworkManager.NetworkMetrics.TrackObjectSpawnReceived(srcClientId, networkObject, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
MockSpawnObject(spawnCommand);
|
||||
}
|
||||
}
|
||||
|
||||
internal void DespawnObject(SnapshotDespawnCommand despawnCommand, ulong srcClientId)
|
||||
{
|
||||
if (m_NetworkManager)
|
||||
{
|
||||
m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId,
|
||||
out NetworkObject networkObject);
|
||||
|
||||
m_NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
|
||||
//todo: discuss with tools how to report shared bytes
|
||||
m_NetworkManager.NetworkMetrics.TrackObjectDestroyReceived(srcClientId, networkObject, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
MockDespawnObject(despawnCommand);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void ReadSpawns(in SnapshotDataMessage message, ulong srcClientId)
|
||||
{
|
||||
SnapshotSpawnCommand spawnCommand;
|
||||
SnapshotDespawnCommand despawnCommand;
|
||||
@@ -436,16 +531,7 @@ namespace Unity.Netcode
|
||||
|
||||
// Debug.Log($"[Spawn] {spawnCommand.NetworkObjectId} {spawnCommand.TickWritten}");
|
||||
|
||||
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId)
|
||||
{
|
||||
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
|
||||
}
|
||||
SpawnObject(spawnCommand, srcClientId);
|
||||
}
|
||||
for (var i = 0; i < message.Despawns.Length; i++)
|
||||
{
|
||||
@@ -461,10 +547,7 @@ namespace Unity.Netcode
|
||||
|
||||
// Debug.Log($"[DeSpawn] {despawnCommand.NetworkObjectId} {despawnCommand.TickWritten}");
|
||||
|
||||
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId,
|
||||
out NetworkObject networkObject);
|
||||
|
||||
NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
|
||||
DespawnObject(despawnCommand, srcClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +652,7 @@ namespace Unity.Netcode
|
||||
/// <param name="key">The key to search for</param>
|
||||
private NetworkVariableBase FindNetworkVar(VariableKey key)
|
||||
{
|
||||
var spawnedObjects = NetworkManager.SpawnManager.SpawnedObjects;
|
||||
var spawnedObjects = m_NetworkManager.SpawnManager.SpawnedObjects;
|
||||
|
||||
if (spawnedObjects.ContainsKey(key.NetworkObjectId))
|
||||
{
|
||||
@@ -580,61 +663,49 @@ namespace Unity.Netcode
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class ClientData
|
||||
{
|
||||
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
|
||||
{
|
||||
internal ulong SequenceNumber;
|
||||
internal ulong ObjectId;
|
||||
internal int Tick;
|
||||
}
|
||||
|
||||
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
|
||||
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
|
||||
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
|
||||
|
||||
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
|
||||
internal int NextDespawnIndex = 0; // same as above, but for despawns.
|
||||
|
||||
// by objectId
|
||||
// which spawns and despawns did this connection ack'ed ?
|
||||
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
|
||||
|
||||
// list of spawn and despawns commands we sent, with sequence number
|
||||
// need to manage acknowledgements
|
||||
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
|
||||
}
|
||||
|
||||
internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
|
||||
{
|
||||
// temporary, debugging sentinels
|
||||
internal const ushort SentinelBefore = 0x4246;
|
||||
internal const ushort SentinelAfter = 0x89CE;
|
||||
|
||||
private NetworkManager m_NetworkManager = default;
|
||||
private Snapshot m_Snapshot = default;
|
||||
|
||||
// by clientId
|
||||
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
|
||||
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
|
||||
|
||||
private int m_CurrentTick = NetworkTickSystem.NoTick;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// Registers the snapshot system for early updates, keeps reference to the NetworkManager
|
||||
internal SnapshotSystem(NetworkManager networkManager)
|
||||
internal SnapshotSystem(NetworkManager networkManager, NetworkConfig config, NetworkTickSystem networkTickSystem)
|
||||
{
|
||||
m_Snapshot = new Snapshot();
|
||||
|
||||
m_NetworkManager = networkManager;
|
||||
m_Snapshot.NetworkManager = networkManager;
|
||||
m_NetworkTickSystem = networkTickSystem;
|
||||
|
||||
m_UseSnapshotDelta = config.UseSnapshotDelta;
|
||||
m_UseSnapshotSpawn = config.UseSnapshotSpawn;
|
||||
m_SnapshotMaxSpawnUsage = config.SnapshotMaxSpawnUsage;
|
||||
|
||||
UpdateClientServerData();
|
||||
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
|
||||
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used
|
||||
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
|
||||
Spawns = new SnapshotSpawnCommand[SpawnsBufferCount];
|
||||
Despawns = new SnapshotDespawnCommand[DespawnsBufferCount];
|
||||
}
|
||||
|
||||
// since we don't want to access the NetworkManager directly, we refresh those values on Update
|
||||
internal void UpdateClientServerData()
|
||||
{
|
||||
if (m_NetworkManager)
|
||||
{
|
||||
IsServer = m_NetworkManager.IsServer;
|
||||
IsConnectedClient = m_NetworkManager.IsConnectedClient;
|
||||
ServerClientId = m_NetworkManager.ServerClientId;
|
||||
|
||||
// todo: This is extremely inefficient. What is the efficient and idiomatic way ?
|
||||
ConnectedClientsId.Clear();
|
||||
if (IsServer)
|
||||
{
|
||||
foreach (var id in m_NetworkManager.ConnectedClientsIds)
|
||||
{
|
||||
ConnectedClientsId.Add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ConnectionRtt GetConnectionRtt(ulong clientId)
|
||||
@@ -658,34 +729,36 @@ namespace Unity.Netcode
|
||||
|
||||
public void NetworkUpdate(NetworkUpdateStage updateStage)
|
||||
{
|
||||
if (!m_NetworkManager.NetworkConfig.UseSnapshotDelta && !m_NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
if (!m_UseSnapshotDelta && !m_UseSnapshotSpawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateStage == NetworkUpdateStage.EarlyUpdate)
|
||||
{
|
||||
var tick = m_NetworkManager.NetworkTickSystem.LocalTime.Tick;
|
||||
UpdateClientServerData();
|
||||
|
||||
var tick = m_NetworkTickSystem.LocalTime.Tick;
|
||||
|
||||
if (tick != m_CurrentTick)
|
||||
{
|
||||
m_CurrentTick = tick;
|
||||
if (m_NetworkManager.IsServer)
|
||||
if (IsServer)
|
||||
{
|
||||
for (int i = 0; i < m_NetworkManager.ConnectedClientsList.Count; i++)
|
||||
for (int i = 0; i < ConnectedClientsId.Count; i++)
|
||||
{
|
||||
var clientId = m_NetworkManager.ConnectedClientsList[i].ClientId;
|
||||
var clientId = ConnectedClientsId[i];
|
||||
|
||||
// don't send to ourselves
|
||||
if (clientId != m_NetworkManager.ServerClientId)
|
||||
if (clientId != ServerClientId)
|
||||
{
|
||||
SendSnapshot(clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_NetworkManager.IsConnectedClient)
|
||||
else if (IsConnectedClient)
|
||||
{
|
||||
SendSnapshot(m_NetworkManager.ServerClientId);
|
||||
SendSnapshot(ServerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,12 +794,12 @@ namespace Unity.Netcode
|
||||
{
|
||||
CurrentTick = m_CurrentTick,
|
||||
Sequence = sequence,
|
||||
Range = (ushort)m_Snapshot.Allocator.Range,
|
||||
Range = (ushort)Allocator.Range,
|
||||
|
||||
// todo --M1--
|
||||
// this sends the whole buffer
|
||||
// we'll need to build a per-client list
|
||||
SendMainBuffer = m_Snapshot.MainBuffer,
|
||||
SendMainBuffer = MainBuffer,
|
||||
|
||||
Ack = new SnapshotDataMessage.AckData
|
||||
{
|
||||
@@ -740,7 +813,14 @@ namespace Unity.Netcode
|
||||
WriteIndex(ref message);
|
||||
WriteSpawns(ref message, clientId);
|
||||
|
||||
if (m_NetworkManager)
|
||||
{
|
||||
m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
MockSendMessage(message, NetworkDelivery.Unreliable, clientId);
|
||||
}
|
||||
|
||||
m_ClientData[clientId].LastReceivedSequence = 0;
|
||||
|
||||
@@ -791,51 +871,51 @@ namespace Unity.Netcode
|
||||
ClientData clientData = m_ClientData[clientId];
|
||||
|
||||
// this is needed because spawns being removed may have reduce the size below LRU position
|
||||
if (m_Snapshot.NumSpawns > 0)
|
||||
if (NumSpawns > 0)
|
||||
{
|
||||
clientData.NextSpawnIndex %= m_Snapshot.NumSpawns;
|
||||
clientData.NextSpawnIndex %= NumSpawns;
|
||||
}
|
||||
else
|
||||
{
|
||||
clientData.NextSpawnIndex = 0;
|
||||
}
|
||||
|
||||
if (m_Snapshot.NumDespawns > 0)
|
||||
if (NumDespawns > 0)
|
||||
{
|
||||
clientData.NextDespawnIndex %= m_Snapshot.NumDespawns;
|
||||
clientData.NextDespawnIndex %= NumDespawns;
|
||||
}
|
||||
else
|
||||
{
|
||||
clientData.NextDespawnIndex = 0;
|
||||
}
|
||||
|
||||
message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(m_Snapshot.NumSpawns, Allocator.TempJob);
|
||||
message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(m_Snapshot.NumDespawns, Allocator.TempJob);
|
||||
message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(NumSpawns, Collections.Allocator.TempJob);
|
||||
message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(NumDespawns, Collections.Allocator.TempJob);
|
||||
var spawnUsage = 0;
|
||||
|
||||
for (var j = 0; j < m_Snapshot.NumSpawns && !overSize; j++)
|
||||
for (var j = 0; j < NumSpawns && !overSize; j++)
|
||||
{
|
||||
var index = clientData.NextSpawnIndex;
|
||||
|
||||
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
|
||||
if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(m_Snapshot.Spawns[index])*/)
|
||||
if (Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(Spawns[index])*/)
|
||||
{
|
||||
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.SpawnData>();
|
||||
|
||||
// limit spawn sizes, compare current pos to very first position we wrote to
|
||||
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
|
||||
if (spawnUsage > m_SnapshotMaxSpawnUsage)
|
||||
{
|
||||
overSize = true;
|
||||
break;
|
||||
}
|
||||
var sentSpawn = m_Snapshot.GetSpawnData(clientData, in m_Snapshot.Spawns[index], out var spawn);
|
||||
var sentSpawn = GetSpawnData(clientData, in Spawns[index], out var spawn);
|
||||
message.Spawns.Add(spawn);
|
||||
|
||||
m_Snapshot.Spawns[index].TimesWritten++;
|
||||
Spawns[index].TimesWritten++;
|
||||
clientData.SentSpawns.Add(sentSpawn);
|
||||
spawnWritten++;
|
||||
}
|
||||
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % m_Snapshot.NumSpawns;
|
||||
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % NumSpawns;
|
||||
}
|
||||
|
||||
// even though we might have a spawn we could not fit, it's possible despawns will fit (they're smaller)
|
||||
@@ -846,28 +926,28 @@ namespace Unity.Netcode
|
||||
// As-is it is overly restrictive but allows us to go forward without the spawn/despawn dependency check
|
||||
// overSize = false;
|
||||
|
||||
for (var j = 0; j < m_Snapshot.NumDespawns && !overSize; j++)
|
||||
for (var j = 0; j < NumDespawns && !overSize; j++)
|
||||
{
|
||||
var index = clientData.NextDespawnIndex;
|
||||
|
||||
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
|
||||
if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(m_Snapshot.Despawns[index])*/)
|
||||
if (Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(Despawns[index])*/)
|
||||
{
|
||||
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.DespawnData>();
|
||||
|
||||
// limit spawn sizes, compare current pos to very first position we wrote to
|
||||
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
|
||||
if (spawnUsage > m_SnapshotMaxSpawnUsage)
|
||||
{
|
||||
overSize = true;
|
||||
break;
|
||||
}
|
||||
var sentDespawn = m_Snapshot.GetDespawnData(clientData, in m_Snapshot.Despawns[index], out var despawn);
|
||||
var sentDespawn = GetDespawnData(clientData, in Despawns[index], out var despawn);
|
||||
message.Despawns.Add(despawn);
|
||||
m_Snapshot.Despawns[index].TimesWritten++;
|
||||
Despawns[index].TimesWritten++;
|
||||
clientData.SentSpawns.Add(sentDespawn);
|
||||
despawnWritten++;
|
||||
}
|
||||
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % m_Snapshot.NumDespawns;
|
||||
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % NumDespawns;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,10 +957,10 @@ namespace Unity.Netcode
|
||||
/// <param name="message">The message to write the index to</param>
|
||||
private void WriteIndex(ref SnapshotDataMessage message)
|
||||
{
|
||||
message.Entries = new NativeList<SnapshotDataMessage.EntryData>(m_Snapshot.LastEntry, Allocator.TempJob);
|
||||
for (var i = 0; i < m_Snapshot.LastEntry; i++)
|
||||
message.Entries = new NativeList<SnapshotDataMessage.EntryData>(LastEntry, Collections.Allocator.TempJob);
|
||||
for (var i = 0; i < LastEntry; i++)
|
||||
{
|
||||
var entryMeta = m_Snapshot.Entries[i];
|
||||
var entryMeta = Entries[i];
|
||||
var entry = entryMeta.Key;
|
||||
message.Entries.Add(new SnapshotDataMessage.EntryData
|
||||
{
|
||||
@@ -897,7 +977,7 @@ namespace Unity.Netcode
|
||||
internal void Spawn(SnapshotSpawnCommand command)
|
||||
{
|
||||
command.TickWritten = m_CurrentTick;
|
||||
m_Snapshot.AddSpawn(command);
|
||||
AddSpawn(command);
|
||||
|
||||
// Debug.Log($"[Spawn] {command.NetworkObjectId} {command.TickWritten}");
|
||||
}
|
||||
@@ -905,7 +985,7 @@ namespace Unity.Netcode
|
||||
internal void Despawn(SnapshotDespawnCommand command)
|
||||
{
|
||||
command.TickWritten = m_CurrentTick;
|
||||
m_Snapshot.AddDespawn(command);
|
||||
AddDespawn(command);
|
||||
|
||||
// Debug.Log($"[DeSpawn] {command.NetworkObjectId} {command.TickWritten}");
|
||||
}
|
||||
@@ -922,35 +1002,35 @@ namespace Unity.Netcode
|
||||
k.NetworkObjectId = networkObjectId;
|
||||
k.BehaviourIndex = (ushort)behaviourIndex;
|
||||
k.VariableIndex = (ushort)variableIndex;
|
||||
k.TickWritten = m_NetworkManager.NetworkTickSystem.LocalTime.Tick;
|
||||
k.TickWritten = m_NetworkTickSystem.LocalTime.Tick;
|
||||
|
||||
int pos = m_Snapshot.Find(k);
|
||||
int pos = Find(k);
|
||||
if (pos == Entry.NotFound)
|
||||
{
|
||||
pos = m_Snapshot.AddEntry(k);
|
||||
pos = AddEntry(k);
|
||||
}
|
||||
|
||||
m_Snapshot.Entries[pos].Key.TickWritten = k.TickWritten;
|
||||
Entries[pos].Key.TickWritten = k.TickWritten;
|
||||
|
||||
WriteVariableToSnapshot(m_Snapshot, networkVariable, pos);
|
||||
WriteVariable(networkVariable, pos);
|
||||
}
|
||||
|
||||
private unsafe void WriteVariableToSnapshot(Snapshot snapshot, NetworkVariableBase networkVariable, int index)
|
||||
private unsafe void WriteVariable(NetworkVariableBase networkVariable, int index)
|
||||
{
|
||||
// write var into buffer, possibly adjusting entry's position and Length
|
||||
var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
|
||||
var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Collections.Allocator.Temp);
|
||||
using (varBuffer)
|
||||
{
|
||||
networkVariable.WriteDelta(varBuffer);
|
||||
if (varBuffer.Length > snapshot.Entries[index].Length)
|
||||
if (varBuffer.Length > Entries[index].Length)
|
||||
{
|
||||
// allocate this Entry's buffer
|
||||
snapshot.AllocateEntry(ref snapshot.Entries[index], index, (int)varBuffer.Length);
|
||||
AllocateEntry(ref Entries[index], index, (int)varBuffer.Length);
|
||||
}
|
||||
|
||||
fixed (byte* buffer = snapshot.MainBuffer)
|
||||
fixed (byte* buffer = MainBuffer)
|
||||
{
|
||||
UnsafeUtility.MemCpy(buffer + snapshot.Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length);
|
||||
UnsafeUtility.MemCpy(buffer + Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1009,10 +1089,10 @@ namespace Unity.Netcode
|
||||
// without this, we incur extra retransmit, not a catastrophic failure
|
||||
}
|
||||
|
||||
m_Snapshot.ReadBuffer(message);
|
||||
m_Snapshot.ReadIndex(message);
|
||||
m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId));
|
||||
m_Snapshot.ReadSpawns(message);
|
||||
ReadBuffer(message);
|
||||
ReadIndex(message);
|
||||
ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId));
|
||||
ReadSpawns(message, clientId);
|
||||
}
|
||||
|
||||
// todo --M1--
|
||||
@@ -1024,14 +1104,14 @@ namespace Unity.Netcode
|
||||
table += $"We're clientId {m_NetworkManager.LocalClientId}\n";
|
||||
|
||||
table += "=== Variables ===\n";
|
||||
for (int i = 0; i < m_Snapshot.LastEntry; i++)
|
||||
for (int i = 0; i < LastEntry; i++)
|
||||
{
|
||||
table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", m_Snapshot.Entries[i].Key.NetworkObjectId, m_Snapshot.Entries[i].Key.BehaviourIndex,
|
||||
m_Snapshot.Entries[i].Key.VariableIndex, m_Snapshot.Entries[i].Position, m_Snapshot.Entries[i].Position + m_Snapshot.Entries[i].Length, m_Snapshot.Entries[i].Key.TickWritten);
|
||||
table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", Entries[i].Key.NetworkObjectId, Entries[i].Key.BehaviourIndex,
|
||||
Entries[i].Key.VariableIndex, Entries[i].Position, Entries[i].Position + Entries[i].Length, Entries[i].Key.TickWritten);
|
||||
|
||||
for (int j = 0; j < m_Snapshot.Entries[i].Length && j < 4; j++)
|
||||
for (int j = 0; j < Entries[i].Length && j < 4; j++)
|
||||
{
|
||||
table += m_Snapshot.MainBuffer[m_Snapshot.Entries[i].Position + j].ToString("X2") + " ";
|
||||
table += MainBuffer[Entries[i].Position + j].ToString("X2") + " ";
|
||||
}
|
||||
|
||||
table += "\n";
|
||||
@@ -1039,14 +1119,14 @@ namespace Unity.Netcode
|
||||
|
||||
table += "=== Spawns ===\n";
|
||||
|
||||
for (int i = 0; i < m_Snapshot.NumSpawns; i++)
|
||||
for (int i = 0; i < NumSpawns; i++)
|
||||
{
|
||||
string targets = "";
|
||||
foreach (var target in m_Snapshot.Spawns[i].TargetClientIds)
|
||||
foreach (var target in Spawns[i].TargetClientIds)
|
||||
{
|
||||
targets += target.ToString() + ", ";
|
||||
}
|
||||
table += $"Spawn Object Id {m_Snapshot.Spawns[i].NetworkObjectId}, Tick {m_Snapshot.Spawns[i].TickWritten}, Target {targets}\n";
|
||||
table += $"Spawn Object Id {Spawns[i].NetworkObjectId}, Tick {Spawns[i].TickWritten}, Target {targets}\n";
|
||||
}
|
||||
|
||||
table += $"=== RTTs ===\n";
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public event UnnamedMessageDelegate OnUnnamedMessage;
|
||||
|
||||
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader)
|
||||
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize)
|
||||
{
|
||||
if (OnUnnamedMessage != null)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ namespace Unity.Netcode
|
||||
((UnnamedMessageDelegate)handler).Invoke(clientId, reader);
|
||||
}
|
||||
}
|
||||
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + serializedHeaderSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,9 +115,9 @@ namespace Unity.Netcode
|
||||
private Dictionary<ulong, string> m_MessageHandlerNameLookup32 = new Dictionary<ulong, string>();
|
||||
private Dictionary<ulong, string> m_MessageHandlerNameLookup64 = new Dictionary<ulong, string>();
|
||||
|
||||
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader)
|
||||
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader, int serializedHeaderSize)
|
||||
{
|
||||
var bytesCount = reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
var bytesCount = reader.Length + serializedHeaderSize;
|
||||
|
||||
if (m_NetworkManager == null)
|
||||
{
|
||||
|
||||
@@ -11,11 +11,12 @@ namespace Unity.Netcode
|
||||
/// unchanged - if new messages are added or messages are removed, MessageType assignments may be
|
||||
/// calculated differently.
|
||||
/// </summary>
|
||||
public byte MessageType;
|
||||
public uint MessageType;
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the message, NOT including the header.
|
||||
/// Stored as a uint to avoid zig-zag encoding, but capped at int.MaxValue.
|
||||
/// </summary>
|
||||
public ushort MessageSize;
|
||||
public uint MessageSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Unity.Netcode
|
||||
var message = new NamedMessage();
|
||||
reader.ReadValueSafe(out message.Hash);
|
||||
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader);
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader, context.SerializedHeaderSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ namespace Unity.Netcode
|
||||
|
||||
public static unsafe void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var message = new SnapshotDataMessage();
|
||||
if (!reader.TryBeginRead(
|
||||
FastBufferWriter.GetWriteSize(message.CurrentTick) +
|
||||
@@ -142,12 +141,16 @@ namespace Unity.Netcode
|
||||
using (message.Spawns)
|
||||
using (message.Despawns)
|
||||
{
|
||||
message.Handle(context.SenderId, networkManager);
|
||||
message.Handle(context.SenderId, context.SystemOwner);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, NetworkManager networkManager)
|
||||
public void Handle(ulong senderId, object systemOwner)
|
||||
{
|
||||
if (systemOwner is NetworkManager)
|
||||
{
|
||||
var networkManager = (NetworkManager)systemOwner;
|
||||
|
||||
// todo: temporary hack around bug
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
@@ -157,5 +160,13 @@ namespace Unity.Netcode
|
||||
var snapshotSystem = networkManager.SnapshotSystem;
|
||||
snapshotSystem.HandleSnapshot(senderId, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
|
||||
var snapshotSystem = ownerData.Item1;
|
||||
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Unity.Netcode
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader);
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader, context.SerializedHeaderSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Unity.Netcode
|
||||
public MessageHeader Header;
|
||||
public ulong SenderId;
|
||||
public float Timestamp;
|
||||
public int MessageHeaderSerializedSize;
|
||||
}
|
||||
|
||||
private struct SendQueueItem
|
||||
@@ -46,27 +47,27 @@ namespace Unity.Netcode
|
||||
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
|
||||
private Type[] m_ReverseTypeMap = new Type[255];
|
||||
|
||||
private Dictionary<Type, byte> m_MessageTypes = new Dictionary<Type, byte>();
|
||||
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
|
||||
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
|
||||
|
||||
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
|
||||
|
||||
private byte m_HighMessageType;
|
||||
private uint m_HighMessageType;
|
||||
private object m_Owner;
|
||||
private IMessageSender m_MessageSender;
|
||||
private bool m_Disposed;
|
||||
|
||||
internal Type[] MessageTypes => m_ReverseTypeMap;
|
||||
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
|
||||
internal int MessageHandlerCount => m_HighMessageType;
|
||||
internal uint MessageHandlerCount => m_HighMessageType;
|
||||
|
||||
internal byte GetMessageType(Type t)
|
||||
internal uint GetMessageType(Type t)
|
||||
{
|
||||
return m_MessageTypes[t];
|
||||
}
|
||||
|
||||
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = 64000;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
|
||||
|
||||
internal struct MessageWithHandler
|
||||
{
|
||||
@@ -100,7 +101,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
if (m_Disposed)
|
||||
{
|
||||
@@ -113,6 +114,14 @@ namespace Unity.Netcode
|
||||
{
|
||||
CleanupDisconnectedClient(kvp.Key);
|
||||
}
|
||||
|
||||
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
|
||||
{
|
||||
// Avoid copies...
|
||||
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(queueIndex);
|
||||
item.Reader.Dispose();
|
||||
}
|
||||
|
||||
m_IncomingMessageQueue.Dispose();
|
||||
m_Disposed = true;
|
||||
}
|
||||
@@ -141,7 +150,7 @@ namespace Unity.Netcode
|
||||
fixed (byte* nativeData = data.Array)
|
||||
{
|
||||
var batchReader =
|
||||
new FastBufferReader(nativeData, Allocator.None, data.Count, data.Offset);
|
||||
new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
|
||||
if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
|
||||
{
|
||||
NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it.");
|
||||
@@ -157,14 +166,23 @@ namespace Unity.Netcode
|
||||
|
||||
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
|
||||
{
|
||||
if (!batchReader.TryBeginRead(sizeof(MessageHeader)))
|
||||
|
||||
var messageHeader = new MessageHeader();
|
||||
var position = batchReader.Position;
|
||||
try
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageType);
|
||||
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageSize);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
|
||||
return;
|
||||
throw;
|
||||
}
|
||||
batchReader.ReadValue(out MessageHeader messageHeader);
|
||||
|
||||
if (!batchReader.TryBeginRead(messageHeader.MessageSize))
|
||||
var receivedHeaderSize = batchReader.Position - position;
|
||||
|
||||
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
|
||||
{
|
||||
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
|
||||
return;
|
||||
@@ -177,9 +195,10 @@ namespace Unity.Netcode
|
||||
// Copy the data for this message into a new FastBufferReader that owns that memory.
|
||||
// We can't guarantee the memory in the ArraySegment stays valid because we don't own it,
|
||||
// so we must move it to memory we do own.
|
||||
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, messageHeader.MessageSize)
|
||||
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, (int)messageHeader.MessageSize),
|
||||
MessageHeaderSerializedSize = receivedHeaderSize,
|
||||
});
|
||||
batchReader.Seek(batchReader.Position + messageHeader.MessageSize);
|
||||
batchReader.Seek(batchReader.Position + (int)messageHeader.MessageSize);
|
||||
}
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
@@ -202,7 +221,7 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp)
|
||||
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
|
||||
{
|
||||
if (header.MessageType >= m_HighMessageType)
|
||||
{
|
||||
@@ -215,8 +234,10 @@ namespace Unity.Netcode
|
||||
SystemOwner = m_Owner,
|
||||
SenderId = senderId,
|
||||
Timestamp = timestamp,
|
||||
Header = header
|
||||
Header = header,
|
||||
SerializedHeaderSize = serializedHeaderSize,
|
||||
};
|
||||
|
||||
var type = m_ReverseTypeMap[header.MessageType];
|
||||
if (!CanReceive(senderId, type))
|
||||
{
|
||||
@@ -228,6 +249,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
}
|
||||
|
||||
var handler = m_MessageHandlers[header.MessageType];
|
||||
using (reader)
|
||||
{
|
||||
@@ -253,11 +275,15 @@ namespace Unity.Netcode
|
||||
|
||||
internal unsafe void ProcessIncomingMessageQueue()
|
||||
{
|
||||
for (var i = 0; i < m_IncomingMessageQueue.Length; ++i)
|
||||
for (var index = 0; index < m_IncomingMessageQueue.Length; ++index)
|
||||
{
|
||||
// Avoid copies...
|
||||
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(i);
|
||||
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp);
|
||||
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(index);
|
||||
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp, item.MessageHeaderSerializedSize);
|
||||
if (m_Disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_IncomingMessageQueue.Clear();
|
||||
@@ -316,11 +342,21 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
|
||||
var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
using (tmpSerializer)
|
||||
{
|
||||
|
||||
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
|
||||
message.Serialize(tmpSerializer);
|
||||
|
||||
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
|
||||
|
||||
var header = new MessageHeader
|
||||
{
|
||||
MessageSize = (ushort)tmpSerializer.Length,
|
||||
MessageType = m_MessageTypes[typeof(TMessageType)],
|
||||
};
|
||||
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageType);
|
||||
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageSize);
|
||||
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
{
|
||||
var clientId = clientIds[i];
|
||||
@@ -347,7 +383,7 @@ namespace Unity.Netcode
|
||||
ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
|
||||
if (lastQueueItem.NetworkDelivery != delivery ||
|
||||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
|
||||
< tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>())
|
||||
< tmpSerializer.Length + headerSerializer.Length)
|
||||
{
|
||||
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
|
||||
maxSize));
|
||||
@@ -356,24 +392,18 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
|
||||
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
var header = new MessageHeader
|
||||
{
|
||||
MessageSize = (ushort)tmpSerializer.Length,
|
||||
MessageType = m_MessageTypes[typeof(TMessageType)],
|
||||
};
|
||||
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
|
||||
|
||||
writeQueueItem.Writer.WriteValue(header);
|
||||
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
|
||||
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
|
||||
writeQueueItem.BatchHeader.BatchSize++;
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + headerSerializer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
}
|
||||
return tmpSerializer.Length + headerSerializer.Length;
|
||||
}
|
||||
|
||||
private struct PointerListWrapper<T> : IReadOnlyList<T>
|
||||
@@ -461,17 +491,17 @@ namespace Unity.Netcode
|
||||
try
|
||||
{
|
||||
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueItem.Writer.Dispose();
|
||||
}
|
||||
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueItem.Writer.Dispose();
|
||||
}
|
||||
}
|
||||
sendQueueItem.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,5 +25,10 @@ namespace Unity.Netcode
|
||||
/// The header data that was sent with the message
|
||||
/// </summary>
|
||||
public MessageHeader Header;
|
||||
|
||||
/// <summary>
|
||||
/// The actual serialized size of the header when packed into the buffer
|
||||
/// </summary>
|
||||
public int SerializedHeaderSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
public static long SafeGetLengthOrDefault(this Stream stream)
|
||||
{
|
||||
return stream.CanSeek ? stream.Length : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,18 +272,22 @@ namespace Unity.Netcode
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
reader.ReadValueSafe(out T value);
|
||||
if (index < m_List.Length)
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
m_List[index] = value;
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
}
|
||||
|
||||
var previousValue = m_List[index];
|
||||
m_List[index] = value;
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,7 +297,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -368,7 +373,7 @@ namespace Unity.Netcode
|
||||
public bool Contains(T item)
|
||||
{
|
||||
int index = NativeArrayExtensions.IndexOf(m_List, item);
|
||||
return index == -1;
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -528,6 +533,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public T Value;
|
||||
|
||||
/// <summary>
|
||||
/// The previous value when "Value" has changed, if available.
|
||||
/// </summary>
|
||||
public T PreviousValue;
|
||||
|
||||
/// <summary>
|
||||
/// the index changed, added or removed if available
|
||||
/// </summary>
|
||||
|
||||
@@ -9,6 +9,55 @@ namespace Unity.Netcode
|
||||
[Serializable]
|
||||
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
|
||||
{
|
||||
// Functions that know how to serialize INetworkSerializable
|
||||
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, ref 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, ref 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, ref TForMethod value);
|
||||
|
||||
internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
|
||||
|
||||
// These static delegates provide the right implementation for writing and reading a particular network variable
|
||||
// type.
|
||||
//
|
||||
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
|
||||
//
|
||||
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
|
||||
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
|
||||
//
|
||||
// In the future we may be able to use this to provide packing implementations for floats and integers to
|
||||
// optimize bandwidth usage.
|
||||
//
|
||||
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
|
||||
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
|
||||
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
|
||||
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
|
||||
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
|
||||
// side, but it gets the best achievable user experience and performance.
|
||||
internal static WriteDelegate<T> Write = WriteValue;
|
||||
internal static ReadDelegate<T> Read = ReadValue;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for value changed event
|
||||
/// </summary>
|
||||
@@ -106,7 +155,7 @@ namespace Unity.Netcode
|
||||
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
||||
{
|
||||
T previousValue = m_InternalValue;
|
||||
reader.ReadValueSafe(out m_InternalValue);
|
||||
Read(reader, out m_InternalValue);
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
@@ -119,13 +168,13 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void ReadField(FastBufferReader reader)
|
||||
{
|
||||
reader.ReadValueSafe(out m_InternalValue);
|
||||
Read(reader, out m_InternalValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteValueSafe(m_InternalValue);
|
||||
Write(writer, ref m_InternalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
Runtime/NetworkVariable/NetworkVariableHelper.cs
Normal file
22
Runtime/NetworkVariable/NetworkVariableHelper.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public class NetworkVariableHelper
|
||||
{
|
||||
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
|
||||
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
|
||||
//
|
||||
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
|
||||
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
|
||||
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
|
||||
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
|
||||
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
|
||||
// side, but it gets the best achievable user experience and performance.
|
||||
//
|
||||
// RuntimeAccessModifiersILPP will make this `public`
|
||||
internal static void InitializeDelegates<T>() where T : unmanaged, INetworkSerializable
|
||||
{
|
||||
NetworkVariable<T>.Write = NetworkVariable<T>.WriteNetworkSerializable;
|
||||
NetworkVariable<T>.Read = NetworkVariable<T>.ReadNetworkSerializable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61dd9b1558f6d7c46ad323b2c2c03c29
|
||||
guid: e54b65208bd3bbe4eaf62ca0384ae21f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af81f9951b096ff4cb8e4f8a4106104a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal static class TypeExtensions
|
||||
{
|
||||
internal static bool HasInterface(this Type type, Type interfaceType)
|
||||
{
|
||||
var ifaces = type.GetInterfaces();
|
||||
for (int i = 0; i < ifaces.Length; i++)
|
||||
{
|
||||
if (ifaces[i] == interfaceType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsNullable(this Type type)
|
||||
{
|
||||
if (!type.IsValueType)
|
||||
{
|
||||
return true; // ref-type
|
||||
}
|
||||
|
||||
return Nullable.GetUnderlyingType(type) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,20 +565,8 @@ namespace Unity.Netcode
|
||||
|
||||
GenerateScenesInBuild();
|
||||
|
||||
// If NetworkManager has this set to true, then we can get the DDOL (DontDestroyOnLoad) from its GaemObject
|
||||
if (networkManager.DontDestroy)
|
||||
{
|
||||
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
|
||||
DontDestroyOnLoadScene = networkManager.gameObject.scene;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, we have to create a GameObject and move it into the DDOL in order to
|
||||
// register the DDOL scene handle with NetworkSceneManager
|
||||
var myDDOLObject = new GameObject("DDOL-NWSM");
|
||||
UnityEngine.Object.DontDestroyOnLoad(myDDOLObject);
|
||||
DontDestroyOnLoadScene = myDDOLObject.scene;
|
||||
UnityEngine.Object.Destroy(myDDOLObject);
|
||||
}
|
||||
|
||||
ServerSceneHandleToClientSceneHandle.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle);
|
||||
ScenesLoaded.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
|
||||
@@ -1028,8 +1016,8 @@ namespace Unity.Netcode
|
||||
// despawned that no longer exists
|
||||
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray());
|
||||
|
||||
//Second, server sets itself as having finished unloading
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
|
||||
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
|
||||
}
|
||||
@@ -1344,8 +1332,8 @@ namespace Unity.Netcode
|
||||
|
||||
OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
|
||||
|
||||
//Second, set the server as having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
|
||||
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
|
||||
}
|
||||
@@ -1830,21 +1818,18 @@ namespace Unity.Netcode
|
||||
/// Moves all NetworkObjects that don't have the <see cref="NetworkObject.DestroyWithScene"/> set to
|
||||
/// the "Do not destroy on load" scene.
|
||||
/// </summary>
|
||||
private void MoveObjectsToDontDestroyOnLoad()
|
||||
internal void MoveObjectsToDontDestroyOnLoad()
|
||||
{
|
||||
// Move ALL NetworkObjects to the temp scene
|
||||
// Move ALL NetworkObjects marked to persist scene transitions into the DDOL scene
|
||||
var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);
|
||||
|
||||
foreach (var sobj in objectsToKeep)
|
||||
{
|
||||
if (!sobj.DestroyWithScene || (sobj.IsSceneObject != null && sobj.IsSceneObject.Value && sobj.gameObject.scene == DontDestroyOnLoadScene))
|
||||
if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene)
|
||||
{
|
||||
// Only move objects with no parent as child objects will follow
|
||||
if (sobj.gameObject.transform.parent == null)
|
||||
// Only move dynamically spawned network objects with no parent as child objects will follow
|
||||
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(sobj.gameObject);
|
||||
// Since we are doing a scene transition, disable the GameObject until the next scene is loaded
|
||||
sobj.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
else if (m_NetworkManager.IsServer)
|
||||
@@ -1907,26 +1892,24 @@ namespace Unity.Netcode
|
||||
/// Moves all spawned NetworkObjects (from do not destroy on load) to the scene specified
|
||||
/// </summary>
|
||||
/// <param name="scene">scene to move the NetworkObjects to</param>
|
||||
private void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
|
||||
internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
|
||||
{
|
||||
// Move ALL NetworkObjects to the temp scene
|
||||
var objectsToKeep = m_NetworkManager.SpawnManager.SpawnedObjectsList;
|
||||
|
||||
foreach (var sobj in objectsToKeep)
|
||||
{
|
||||
if (sobj.gameObject.scene == DontDestroyOnLoadScene && (sobj.IsSceneObject == null || sobj.IsSceneObject.Value))
|
||||
// If it is in the DDOL then
|
||||
if (sobj.gameObject.scene == DontDestroyOnLoadScene)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only move objects with no parent as child objects will follow
|
||||
if (sobj.gameObject.transform.parent == null)
|
||||
// only move dynamically spawned network objects, with no parent as child objects will follow,
|
||||
// back into the currently active scene
|
||||
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
|
||||
{
|
||||
// set it back to active at this point
|
||||
sobj.gameObject.SetActive(true);
|
||||
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace Unity.Netcode
|
||||
var newSize = Math.Min(desiredSize, Handle->MaxCapacity);
|
||||
byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf<byte>(), Handle->Allocator);
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
UnsafeUtility.MemSet(newBuffer, 0, sizeof(WriterHandle) + newSize);
|
||||
UnsafeUtility.MemSet(newBuffer, 0, newSize);
|
||||
#endif
|
||||
UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length);
|
||||
if (Handle->BufferGrew)
|
||||
@@ -428,7 +428,7 @@ namespace Unity.Netcode
|
||||
/// <param name="count"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void WriteNetworkSerializable<T>(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable
|
||||
public void WriteNetworkSerializable<T>(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable
|
||||
{
|
||||
int sizeInTs = count != -1 ? count : array.Length - offset;
|
||||
WriteValueSafe(sizeInTs);
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Unity.Netcode
|
||||
public MessageHeader Header;
|
||||
public ulong SenderId;
|
||||
public float Timestamp;
|
||||
public int SerializedHeaderSize;
|
||||
}
|
||||
private struct TriggerInfo
|
||||
{
|
||||
@@ -117,7 +118,8 @@ namespace Unity.Netcode
|
||||
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
|
||||
Header = context.Header,
|
||||
Timestamp = context.Timestamp,
|
||||
SenderId = context.SenderId
|
||||
SenderId = context.SenderId,
|
||||
SerializedHeaderSize = context.SerializedHeaderSize
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,6 +156,24 @@ namespace Unity.Netcode
|
||||
m_Triggers.Remove(staleKeys[i]);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
internal void CleanupAllTriggers()
|
||||
{
|
||||
foreach (var kvp in m_Triggers)
|
||||
{
|
||||
foreach (var data in kvp.Value.TriggerData)
|
||||
{
|
||||
data.Reader.Dispose();
|
||||
}
|
||||
|
||||
kvp.Value.TriggerData.Dispose();
|
||||
}
|
||||
|
||||
m_Triggers.Clear();
|
||||
}
|
||||
|
||||
internal void RemoveOwnership(NetworkObject networkObject)
|
||||
{
|
||||
@@ -167,13 +187,21 @@ namespace Unity.Netcode
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
for (int i = NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.Count - 1;
|
||||
i > -1;
|
||||
i--)
|
||||
// If we made it here then we are the server and if the server is determined to already be the owner
|
||||
// then ignore the RemoveOwnership invocation.
|
||||
if (networkObject.OwnerClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
if (NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects[i] == networkObject)
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the connected client entry exists before trying to remove ownership.
|
||||
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
{
|
||||
NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.RemoveAt(i);
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
|
||||
{
|
||||
if (networkClient.OwnedObjects[i] == networkObject)
|
||||
{
|
||||
networkClient.OwnedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +219,14 @@ namespace Unity.Netcode
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to get a network client for a clientId from the NetworkManager.
|
||||
@@ -207,7 +243,7 @@ namespace Unity.Netcode
|
||||
return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient);
|
||||
}
|
||||
|
||||
if (clientId == NetworkManager.LocalClient.ClientId)
|
||||
if (NetworkManager.LocalClient != null && clientId == NetworkManager.LocalClient.ClientId)
|
||||
{
|
||||
networkClient = NetworkManager.LocalClient;
|
||||
return true;
|
||||
@@ -483,7 +519,7 @@ namespace Unity.Netcode
|
||||
foreach (var trigger in triggerInfo.TriggerData)
|
||||
{
|
||||
// Reader will be disposed within HandleMessage
|
||||
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp);
|
||||
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp, trigger.SerializedHeaderSize);
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Dispose();
|
||||
@@ -580,20 +616,29 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void DestroyNonSceneObjects()
|
||||
internal void DespawnAndDestroyNetworkObjects()
|
||||
{
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
||||
|
||||
for (int i = 0; i < networkObjects.Length; i++)
|
||||
{
|
||||
if (networkObjects[i].NetworkManager == NetworkManager)
|
||||
{
|
||||
if (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value == false)
|
||||
{
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
|
||||
{
|
||||
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
|
||||
OnDespawnObject(networkObjects[i], false);
|
||||
// Leave destruction up to the handler
|
||||
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
|
||||
}
|
||||
else if (networkObjects[i].IsSpawned)
|
||||
{
|
||||
// If it is an in-scene placed NetworkObject then just despawn
|
||||
// and let it be destroyed when the scene is unloaded. Otherwise,
|
||||
// despawn and destroy it.
|
||||
var shouldDestroy = !(networkObjects[i].IsSceneObject != null
|
||||
&& networkObjects[i].IsSceneObject.Value);
|
||||
|
||||
OnDespawnObject(networkObjects[i], shouldDestroy);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -602,7 +647,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void DestroySceneObjects()
|
||||
{
|
||||
@@ -668,6 +712,9 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are shutting down the NetworkManager, then ignore resetting the parent
|
||||
if (!NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
|
||||
foreach (var spawnedNetObj in SpawnedObjectsList)
|
||||
{
|
||||
@@ -682,6 +729,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
{
|
||||
|
||||
@@ -114,6 +114,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
double d = m_TimeSec / m_TickInterval;
|
||||
m_CachedTick = (int)d;
|
||||
// This check is needed due to double division imprecision of large numbers
|
||||
if ((d - m_CachedTick) >= 0.999999999999)
|
||||
{
|
||||
m_CachedTick++;
|
||||
}
|
||||
m_CachedTickOffset = ((d - Math.Truncate(d)) * m_TickInterval);
|
||||
|
||||
// This handles negative time, decreases tick by 1 and makes offset positive.
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Unity.Netcode.Samples
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
CanCommitToTransform = IsOwner;
|
||||
base.Update();
|
||||
if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
|
||||
{
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Unity.Netcode.EditorTests
|
||||
var reader = new FastBufferReader(writer, Allocator.Temp);
|
||||
using (reader)
|
||||
{
|
||||
m_MessagingSystem.HandleMessage(messageHeader, reader, 0, 0);
|
||||
m_MessagingSystem.HandleMessage(messageHeader, reader, 0, 0, 0);
|
||||
Assert.IsTrue(TestMessage.Deserialized);
|
||||
Assert.AreEqual(1, TestMessage.DeserializedValues.Count);
|
||||
Assert.AreEqual(message, TestMessage.DeserializedValues[0]);
|
||||
@@ -143,7 +143,7 @@ namespace Unity.Netcode.EditorTests
|
||||
};
|
||||
var messageHeader = new MessageHeader
|
||||
{
|
||||
MessageSize = (ushort)UnsafeUtility.SizeOf<TestMessage>(),
|
||||
MessageSize = (uint)UnsafeUtility.SizeOf<TestMessage>(),
|
||||
MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)),
|
||||
};
|
||||
var message = GetMessage();
|
||||
@@ -151,12 +151,10 @@ namespace Unity.Netcode.EditorTests
|
||||
var writer = new FastBufferWriter(1300, Allocator.Temp);
|
||||
using (writer)
|
||||
{
|
||||
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) +
|
||||
FastBufferWriter.GetWriteSize(messageHeader) +
|
||||
FastBufferWriter.GetWriteSize(message));
|
||||
writer.WriteValue(batchHeader);
|
||||
writer.WriteValue(messageHeader);
|
||||
writer.WriteValue(message);
|
||||
writer.WriteValueSafe(batchHeader);
|
||||
BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType);
|
||||
BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize);
|
||||
writer.WriteValueSafe(message);
|
||||
|
||||
var reader = new FastBufferReader(writer, Allocator.Temp);
|
||||
using (reader)
|
||||
@@ -188,14 +186,13 @@ namespace Unity.Netcode.EditorTests
|
||||
var writer = new FastBufferWriter(1300, Allocator.Temp);
|
||||
using (writer)
|
||||
{
|
||||
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) +
|
||||
FastBufferWriter.GetWriteSize(messageHeader) * 2 +
|
||||
FastBufferWriter.GetWriteSize(message) * 2);
|
||||
writer.WriteValue(batchHeader);
|
||||
writer.WriteValue(messageHeader);
|
||||
writer.WriteValue(message);
|
||||
writer.WriteValue(messageHeader);
|
||||
writer.WriteValue(message2);
|
||||
writer.WriteValueSafe(batchHeader);
|
||||
BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType);
|
||||
BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize);
|
||||
writer.WriteValueSafe(message);
|
||||
BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType);
|
||||
BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize);
|
||||
writer.WriteValueSafe(message2);
|
||||
|
||||
var reader = new FastBufferReader(writer, Allocator.Temp);
|
||||
using (reader)
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Unity.Netcode.EditorTests
|
||||
public void WhenNotExceedingBatchSize_NewBatchesAreNotCreated()
|
||||
{
|
||||
var message = GetMessage();
|
||||
var size = UnsafeUtility.SizeOf<TestMessage>() + UnsafeUtility.SizeOf<MessageHeader>();
|
||||
var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
|
||||
for (var i = 0; i < 1300 / size; ++i)
|
||||
{
|
||||
m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients);
|
||||
@@ -138,7 +138,7 @@ namespace Unity.Netcode.EditorTests
|
||||
public void WhenExceedingBatchSize_NewBatchesAreCreated()
|
||||
{
|
||||
var message = GetMessage();
|
||||
var size = UnsafeUtility.SizeOf<TestMessage>() + UnsafeUtility.SizeOf<MessageHeader>();
|
||||
var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
|
||||
for (var i = 0; i < (1300 / size) + 1; ++i)
|
||||
{
|
||||
m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients);
|
||||
@@ -152,7 +152,7 @@ namespace Unity.Netcode.EditorTests
|
||||
public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated()
|
||||
{
|
||||
var message = GetMessage();
|
||||
var size = UnsafeUtility.SizeOf<TestMessage>() + UnsafeUtility.SizeOf<MessageHeader>();
|
||||
var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
|
||||
for (var i = 0; i < (1300 / size) + 1; ++i)
|
||||
{
|
||||
m_MessagingSystem.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, m_Clients);
|
||||
@@ -198,24 +198,25 @@ namespace Unity.Netcode.EditorTests
|
||||
var reader = new FastBufferReader(m_MessageSender.MessageQueue[0], Allocator.Temp);
|
||||
using (reader)
|
||||
{
|
||||
reader.TryBeginRead(
|
||||
FastBufferWriter.GetWriteSize<BatchHeader>() +
|
||||
FastBufferWriter.GetWriteSize<MessageHeader>() * 2 +
|
||||
FastBufferWriter.GetWriteSize<TestMessage>() * 2
|
||||
);
|
||||
reader.ReadValue(out BatchHeader header);
|
||||
reader.ReadValueSafe(out BatchHeader header);
|
||||
Assert.AreEqual(2, header.BatchSize);
|
||||
|
||||
reader.ReadValue(out MessageHeader messageHeader);
|
||||
MessageHeader messageHeader;
|
||||
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageType);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageSize);
|
||||
|
||||
Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader.MessageType);
|
||||
Assert.AreEqual(UnsafeUtility.SizeOf<TestMessage>(), messageHeader.MessageSize);
|
||||
reader.ReadValue(out TestMessage receivedMessage);
|
||||
reader.ReadValueSafe(out TestMessage receivedMessage);
|
||||
Assert.AreEqual(message, receivedMessage);
|
||||
|
||||
reader.ReadValue(out MessageHeader messageHeader2);
|
||||
Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader2.MessageType);
|
||||
Assert.AreEqual(UnsafeUtility.SizeOf<TestMessage>(), messageHeader2.MessageSize);
|
||||
reader.ReadValue(out TestMessage receivedMessage2);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageType);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageSize);
|
||||
|
||||
Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader.MessageType);
|
||||
Assert.AreEqual(UnsafeUtility.SizeOf<TestMessage>(), messageHeader.MessageSize);
|
||||
reader.ReadValueSafe(out TestMessage receivedMessage2);
|
||||
Assert.AreEqual(message2, receivedMessage2);
|
||||
}
|
||||
}
|
||||
|
||||
38
Tests/Editor/NetworkManagerConfigurationTests.cs
Normal file
38
Tests/Editor/NetworkManagerConfigurationTests.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using Unity.Netcode.Editor;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Netcode.EditorTests
|
||||
{
|
||||
public class NetworkManagerConfigurationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Does a simple check to make sure the nested network manager will
|
||||
/// notify the user when in the editor. This is just a unit test to
|
||||
/// validate this is functioning
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NestedNetworkManagerCheck()
|
||||
{
|
||||
var parent = new GameObject("ParentObject");
|
||||
var networkManagerObject = new GameObject(nameof(NestedNetworkManagerCheck));
|
||||
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
||||
|
||||
// Make our NetworkManager's GameObject nested
|
||||
networkManagerObject.transform.parent = parent.transform;
|
||||
|
||||
// Pre-generate the error message we are expecting to see
|
||||
var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform);
|
||||
|
||||
// Trap for the nested NetworkManager exception
|
||||
LogAssert.Expect(LogType.Error, messageToCheck);
|
||||
|
||||
// Since this is an in-editor test, we must force this invocation
|
||||
NetworkManagerHelper.Singleton.NotifyUserOfNestedNetworkManager(networkManager, false, true);
|
||||
|
||||
// Clean up
|
||||
Object.DestroyImmediate(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8514b4eca0c7044d9b92faf9407ec93
|
||||
guid: 9b84044fccbd3cd49908f0efd5719347
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -9,7 +9,7 @@ namespace Unity.Netcode.EditorTests
|
||||
[Test]
|
||||
public void TestBasicRtt()
|
||||
{
|
||||
var snapshot = new SnapshotSystem(default);
|
||||
var snapshot = new SnapshotSystem(null, new NetworkConfig(), null);
|
||||
var client1 = snapshot.GetConnectionRtt(0);
|
||||
|
||||
client1.NotifySend(0, 0.0);
|
||||
@@ -40,7 +40,7 @@ namespace Unity.Netcode.EditorTests
|
||||
[Test]
|
||||
public void TestEdgeCasesRtt()
|
||||
{
|
||||
var snapshot = new SnapshotSystem(NetworkManager.Singleton);
|
||||
var snapshot = new SnapshotSystem(null, new NetworkConfig(), null);
|
||||
var client1 = snapshot.GetConnectionRtt(0);
|
||||
var iterationCount = NetworkConfig.RttWindowSize * 3;
|
||||
var extraCount = NetworkConfig.RttWindowSize * 2;
|
||||
|
||||
372
Tests/Editor/SnapshotTests.cs
Normal file
372
Tests/Editor/SnapshotTests.cs
Normal file
@@ -0,0 +1,372 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using NUnit.Framework;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace Unity.Netcode.EditorTests
|
||||
{
|
||||
public class SnapshotTests
|
||||
{
|
||||
private SnapshotSystem m_SendSnapshot;
|
||||
private SnapshotSystem m_RecvSnapshot;
|
||||
|
||||
private NetworkTimeSystem m_SendTimeSystem;
|
||||
private NetworkTickSystem m_SendTickSystem;
|
||||
private NetworkTimeSystem m_RecvTimeSystem;
|
||||
private NetworkTickSystem m_RecvTickSystem;
|
||||
|
||||
private int m_SpawnedObjectCount;
|
||||
private int m_DespawnedObjectCount;
|
||||
private int m_NextSequence;
|
||||
private uint m_TicksPerSec = 15;
|
||||
private int m_MinSpawns;
|
||||
private int m_MinDespawns;
|
||||
|
||||
private bool m_ExpectSpawns;
|
||||
private bool m_ExpectDespawns;
|
||||
private bool m_LoseNextMessage;
|
||||
private bool m_PassBackResponses;
|
||||
|
||||
public void Prepare()
|
||||
{
|
||||
PrepareSendSideSnapshot();
|
||||
PrepareRecvSideSnapshot();
|
||||
}
|
||||
|
||||
public void AdvanceOneTickSendSide()
|
||||
{
|
||||
m_SendTimeSystem.Advance(1.0f / m_TicksPerSec);
|
||||
m_SendTickSystem.UpdateTick(m_SendTimeSystem.LocalTime, m_SendTimeSystem.ServerTime);
|
||||
m_SendSnapshot.NetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
}
|
||||
|
||||
public void AdvanceOneTickRecvSide()
|
||||
{
|
||||
m_RecvTimeSystem.Advance(1.0f / m_TicksPerSec);
|
||||
m_RecvTickSystem.UpdateTick(m_RecvTimeSystem.LocalTime, m_RecvTimeSystem.ServerTime);
|
||||
m_RecvSnapshot.NetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
}
|
||||
|
||||
public void AdvanceOneTick()
|
||||
{
|
||||
AdvanceOneTickSendSide();
|
||||
AdvanceOneTickRecvSide();
|
||||
}
|
||||
|
||||
internal int SpawnObject(SnapshotSpawnCommand command)
|
||||
{
|
||||
m_SpawnedObjectCount++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal int DespawnObject(SnapshotDespawnCommand command)
|
||||
{
|
||||
m_DespawnedObjectCount++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal int SendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId)
|
||||
{
|
||||
if (!m_PassBackResponses)
|
||||
{
|
||||
// we're not ack'ing anything, so those should stay 0
|
||||
Debug.Assert(message.Ack.LastReceivedSequence == 0);
|
||||
}
|
||||
|
||||
Debug.Assert(message.Ack.ReceivedSequenceMask == 0);
|
||||
Debug.Assert(message.Sequence == m_NextSequence); // sequence has to be the expected one
|
||||
|
||||
if (m_ExpectSpawns)
|
||||
{
|
||||
Debug.Assert(message.Spawns.Length >= m_MinSpawns); // there has to be multiple spawns per SnapshotMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(message.Spawns.Length == 0); // Spawns were not expected
|
||||
}
|
||||
|
||||
if (m_ExpectDespawns)
|
||||
{
|
||||
Debug.Assert(message.Despawns.Length >= m_MinDespawns); // there has to be multiple despawns per SnapshotMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(message.Despawns.IsEmpty); // this test should not have despawns
|
||||
}
|
||||
|
||||
Debug.Assert(message.Entries.Length == 0);
|
||||
|
||||
m_NextSequence++;
|
||||
|
||||
if (!m_LoseNextMessage)
|
||||
{
|
||||
using var writer = new FastBufferWriter(1024, Allocator.Temp);
|
||||
message.Serialize(writer);
|
||||
using var reader = new FastBufferReader(writer, Allocator.Temp);
|
||||
var context = new NetworkContext { SenderId = 0, Timestamp = 0.0f, SystemOwner = new Tuple<SnapshotSystem, ulong>(m_RecvSnapshot, 0) };
|
||||
SnapshotDataMessage.Receive(reader, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Spawns.Dispose();
|
||||
message.Despawns.Dispose();
|
||||
message.Entries.Dispose();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal int SendMessageRecvSide(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId)
|
||||
{
|
||||
if (m_PassBackResponses)
|
||||
{
|
||||
using var writer = new FastBufferWriter(1024, Allocator.Temp);
|
||||
message.Serialize(writer);
|
||||
using var reader = new FastBufferReader(writer, Allocator.Temp);
|
||||
var context = new NetworkContext { SenderId = 0, Timestamp = 0.0f, SystemOwner = new Tuple<SnapshotSystem, ulong>(m_SendSnapshot, 1) };
|
||||
SnapshotDataMessage.Receive(reader, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Spawns.Dispose();
|
||||
message.Despawns.Dispose();
|
||||
message.Entries.Dispose();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private void PrepareSendSideSnapshot()
|
||||
{
|
||||
var config = new NetworkConfig();
|
||||
|
||||
m_SendTickSystem = new NetworkTickSystem(m_TicksPerSec, 0.0, 0.0);
|
||||
m_SendTimeSystem = new NetworkTimeSystem(0.2, 0.2, 1.0);
|
||||
|
||||
config.UseSnapshotDelta = false;
|
||||
config.UseSnapshotSpawn = true;
|
||||
|
||||
m_SendSnapshot = new SnapshotSystem(null, config, m_SendTickSystem);
|
||||
|
||||
m_SendSnapshot.IsServer = true;
|
||||
m_SendSnapshot.IsConnectedClient = false;
|
||||
m_SendSnapshot.ServerClientId = 0;
|
||||
m_SendSnapshot.ConnectedClientsId.Clear();
|
||||
m_SendSnapshot.ConnectedClientsId.Add(0);
|
||||
m_SendSnapshot.ConnectedClientsId.Add(1);
|
||||
m_SendSnapshot.MockSendMessage = SendMessage;
|
||||
m_SendSnapshot.MockSpawnObject = SpawnObject;
|
||||
m_SendSnapshot.MockDespawnObject = DespawnObject;
|
||||
}
|
||||
|
||||
private void PrepareRecvSideSnapshot()
|
||||
{
|
||||
var config = new NetworkConfig();
|
||||
|
||||
m_RecvTickSystem = new NetworkTickSystem(m_TicksPerSec, 0.0, 0.0);
|
||||
m_RecvTimeSystem = new NetworkTimeSystem(0.2, 0.2, 1.0);
|
||||
|
||||
config.UseSnapshotDelta = false;
|
||||
config.UseSnapshotSpawn = true;
|
||||
|
||||
m_RecvSnapshot = new SnapshotSystem(null, config, m_RecvTickSystem);
|
||||
|
||||
m_RecvSnapshot.IsServer = false;
|
||||
m_RecvSnapshot.IsConnectedClient = true;
|
||||
m_RecvSnapshot.ServerClientId = 0;
|
||||
m_RecvSnapshot.ConnectedClientsId.Clear();
|
||||
m_SendSnapshot.ConnectedClientsId.Add(0);
|
||||
m_SendSnapshot.ConnectedClientsId.Add(1);
|
||||
m_RecvSnapshot.MockSendMessage = SendMessageRecvSide;
|
||||
m_RecvSnapshot.MockSpawnObject = SpawnObject;
|
||||
m_RecvSnapshot.MockDespawnObject = DespawnObject;
|
||||
}
|
||||
|
||||
private void SendSpawnToSnapshot(ulong objectId)
|
||||
{
|
||||
SnapshotSpawnCommand command = default;
|
||||
// identity
|
||||
command.NetworkObjectId = objectId;
|
||||
// archetype
|
||||
command.GlobalObjectIdHash = 0;
|
||||
command.IsSceneObject = true;
|
||||
// parameters
|
||||
command.IsPlayerObject = false;
|
||||
command.OwnerClientId = 0;
|
||||
command.ParentNetworkId = 0;
|
||||
command.ObjectPosition = default;
|
||||
command.ObjectRotation = default;
|
||||
command.ObjectScale = new Vector3(1.0f, 1.0f, 1.0f);
|
||||
command.TargetClientIds = new List<ulong> { 1 };
|
||||
m_SendSnapshot.Spawn(command);
|
||||
}
|
||||
|
||||
private void SendDespawnToSnapshot(ulong objectId)
|
||||
{
|
||||
SnapshotDespawnCommand command = default;
|
||||
// identity
|
||||
command.NetworkObjectId = objectId;
|
||||
command.TargetClientIds = new List<ulong> { 1 };
|
||||
m_SendSnapshot.Despawn(command);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSnapshotSpawn()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
m_SpawnedObjectCount = 0;
|
||||
m_NextSequence = 0;
|
||||
m_ExpectSpawns = true;
|
||||
m_ExpectDespawns = false;
|
||||
m_MinSpawns = 2; // many spawns are to be sent together
|
||||
m_LoseNextMessage = false;
|
||||
m_PassBackResponses = false;
|
||||
|
||||
var ticksToRun = 20;
|
||||
|
||||
// spawns one more than current buffer size
|
||||
var objectsToSpawn = m_SendSnapshot.SpawnsBufferCount + 1;
|
||||
|
||||
for (int i = 0; i < objectsToSpawn; i++)
|
||||
{
|
||||
SendSpawnToSnapshot((ulong)i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ticksToRun; i++)
|
||||
{
|
||||
AdvanceOneTick();
|
||||
}
|
||||
|
||||
Debug.Assert(m_SpawnedObjectCount == objectsToSpawn);
|
||||
Debug.Assert(m_SendSnapshot.SpawnsBufferCount > objectsToSpawn); // spawn buffer should have grown
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSnapshotSpawnDespawns()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
// test that buffers actually shrink and will grow back to needed size
|
||||
m_SendSnapshot.ReduceBufferUsage();
|
||||
m_RecvSnapshot.ReduceBufferUsage();
|
||||
Debug.Assert(m_SendSnapshot.SpawnsBufferCount == 1);
|
||||
Debug.Assert(m_SendSnapshot.DespawnsBufferCount == 1);
|
||||
Debug.Assert(m_RecvSnapshot.SpawnsBufferCount == 1);
|
||||
Debug.Assert(m_RecvSnapshot.DespawnsBufferCount == 1);
|
||||
|
||||
m_SpawnedObjectCount = 0;
|
||||
m_DespawnedObjectCount = 0;
|
||||
|
||||
m_NextSequence = 0;
|
||||
m_ExpectSpawns = true;
|
||||
m_ExpectDespawns = false;
|
||||
m_MinDespawns = 2; // many despawns are to be sent together
|
||||
m_LoseNextMessage = false;
|
||||
m_PassBackResponses = false;
|
||||
|
||||
var ticksToRun = 20;
|
||||
|
||||
// spawns one more than current buffer size
|
||||
var objectsToSpawn = 10;
|
||||
|
||||
for (int i = 0; i < objectsToSpawn; i++)
|
||||
{
|
||||
SendSpawnToSnapshot((ulong)i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ticksToRun; i++)
|
||||
{
|
||||
AdvanceOneTick();
|
||||
}
|
||||
|
||||
for (int i = 0; i < objectsToSpawn; i++)
|
||||
{
|
||||
SendDespawnToSnapshot((ulong)i);
|
||||
}
|
||||
|
||||
m_ExpectSpawns = true; // the un'acked spawns will still be present
|
||||
m_MinSpawns = 1; // but we don't really care how they are grouped then
|
||||
m_ExpectDespawns = true;
|
||||
|
||||
for (int i = 0; i < ticksToRun; i++)
|
||||
{
|
||||
AdvanceOneTick();
|
||||
}
|
||||
|
||||
Debug.Assert(m_DespawnedObjectCount == objectsToSpawn);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSnapshotMessageLoss()
|
||||
{
|
||||
var r = new Random();
|
||||
Prepare();
|
||||
|
||||
m_SpawnedObjectCount = 0;
|
||||
m_NextSequence = 0;
|
||||
m_ExpectSpawns = true;
|
||||
m_ExpectDespawns = false;
|
||||
m_MinSpawns = 1;
|
||||
m_LoseNextMessage = false;
|
||||
m_PassBackResponses = false;
|
||||
|
||||
var ticksToRun = 10;
|
||||
|
||||
for (int i = 0; i < ticksToRun; i++)
|
||||
{
|
||||
m_LoseNextMessage = (r.Next() % 2) > 0;
|
||||
|
||||
SendSpawnToSnapshot((ulong)i);
|
||||
AdvanceOneTick();
|
||||
}
|
||||
|
||||
m_LoseNextMessage = false;
|
||||
AdvanceOneTick();
|
||||
AdvanceOneTick();
|
||||
|
||||
Debug.Assert(m_SpawnedObjectCount == ticksToRun);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSnapshotAcks()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
m_SpawnedObjectCount = 0;
|
||||
m_NextSequence = 0;
|
||||
m_ExpectSpawns = true;
|
||||
m_ExpectDespawns = false;
|
||||
m_MinSpawns = 1;
|
||||
m_LoseNextMessage = false;
|
||||
m_PassBackResponses = true;
|
||||
|
||||
var objectsToSpawn = 10;
|
||||
|
||||
for (int i = 0; i < objectsToSpawn; i++)
|
||||
{
|
||||
SendSpawnToSnapshot((ulong)i);
|
||||
}
|
||||
AdvanceOneTickSendSide(); // let's tick the send multiple time, to check it still tries to send
|
||||
AdvanceOneTick();
|
||||
|
||||
m_ExpectSpawns = false; // all spawns should have made it back and forth and be absent from next messages
|
||||
AdvanceOneTick();
|
||||
|
||||
for (int i = 0; i < objectsToSpawn; i++)
|
||||
{
|
||||
SendDespawnToSnapshot((ulong)i);
|
||||
}
|
||||
|
||||
m_ExpectDespawns = true; // we should now be seeing despawns
|
||||
AdvanceOneTickSendSide(); // let's tick the send multiple time, to check it still tries to send
|
||||
AdvanceOneTick();
|
||||
|
||||
Debug.Assert(m_SpawnedObjectCount == objectsToSpawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e946a6fdcfcb9dd48b76b38871c0a77b
|
||||
guid: 3d41788be1de34b7c8bcfce6a2877754
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -45,13 +45,26 @@ namespace Unity.Netcode.EditorTests
|
||||
public void TestFlagShutdown()
|
||||
{
|
||||
m_NetworkManager.StartServer();
|
||||
m_NetworkManager.Shutdown();
|
||||
m_NetworkManager.ShutdownInternal();
|
||||
|
||||
Assert.False(m_NetworkManager.IsServer);
|
||||
Assert.False(m_NetworkManager.IsClient);
|
||||
Assert.False(m_NetworkManager.IsHost);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShutdownWithoutStartForExceptions()
|
||||
{
|
||||
m_NetworkManager.ShutdownInternal();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShutdownWithoutConfigForExceptions()
|
||||
{
|
||||
m_NetworkManager.NetworkConfig = null;
|
||||
m_NetworkManager.ShutdownInternal();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
|
||||
@@ -198,6 +198,39 @@ namespace Unity.Netcode.EditorTests
|
||||
Assert.IsTrue(Approximately(startTime.Time, (startTime2 - dif).Time, maxAcceptableTotalOffset));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NetworkTickAdvanceTest()
|
||||
{
|
||||
var shortSteps = Enumerable.Repeat(1 / 30f, 1000);
|
||||
NetworkTickAdvanceTestInternal(shortSteps, 30, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
private NetworkTickSystem m_TickSystem;
|
||||
private NetworkTimeSystem m_TimeSystem;
|
||||
private int m_PreviousTick;
|
||||
|
||||
private void NetworkTickAdvanceTestInternal(IEnumerable<float> steps, uint tickRate, float start, float start2 = 0f)
|
||||
{
|
||||
m_PreviousTick = 0;
|
||||
m_TickSystem = new NetworkTickSystem(tickRate, start, start2);
|
||||
m_TimeSystem = NetworkTimeSystem.ServerTimeSystem();
|
||||
|
||||
m_TickSystem.Tick += TickUpdate;
|
||||
foreach (var step in steps)
|
||||
{
|
||||
m_TimeSystem.Advance(step);
|
||||
m_TickSystem.UpdateTick(m_TimeSystem.LocalTime, m_TimeSystem.ServerTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void TickUpdate()
|
||||
{
|
||||
// Make sure our tick is precisely 1 + m_PreviousTick
|
||||
Assert.IsTrue(m_TickSystem.LocalTime.Tick == m_PreviousTick + 1);
|
||||
// Assign the m_PreviousTick value for next tick check
|
||||
m_PreviousTick = m_TickSystem.LocalTime.Tick;
|
||||
}
|
||||
|
||||
private static bool Approximately(double a, double b, double epsilon = 0.000001d)
|
||||
{
|
||||
var dif = Math.Abs(a - b);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "Unity.Netcode.EditorTests",
|
||||
"rootNamespace": "Unity.Netcode.EditorTests",
|
||||
"references": [
|
||||
"Unity.Collections",
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Netcode.Editor",
|
||||
"Unity.Netcode.Components",
|
||||
|
||||
@@ -15,8 +15,10 @@ namespace Unity.Netcode.RuntimeTests.Metrics
|
||||
public class MessagingMetricsTests : DualClientMetricTestBase
|
||||
{
|
||||
private const uint k_MessageNameHashSize = 8;
|
||||
private static readonly int k_NamedMessageOverhead = (int)k_MessageNameHashSize + FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
private static readonly int k_UnnamedMessageOverhead = FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
// Header is dynamically sized due to packing, will be 2 bytes for all test messages.
|
||||
private const int k_MessageHeaderSize = 2;
|
||||
private static readonly int k_NamedMessageOverhead = (int)k_MessageNameHashSize + k_MessageHeaderSize;
|
||||
private static readonly int k_UnnamedMessageOverhead = k_MessageHeaderSize;
|
||||
|
||||
protected override int NbClients => 2;
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Unity.Netcode.RuntimeTests.Metrics
|
||||
{
|
||||
private const string k_NewNetworkObjectName = "TestNetworkObjectToSpawn";
|
||||
private NetworkObject m_NewNetworkPrefab;
|
||||
// Header is dynamically sized due to packing, will be 2 bytes for all test messages.
|
||||
private const int k_MessageHeaderSize = 2;
|
||||
|
||||
protected override Action<GameObject> UpdatePlayerPrefab => _ =>
|
||||
{
|
||||
@@ -58,7 +60,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
|
||||
var ownershipChangeSent = metricValues.First();
|
||||
Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId);
|
||||
Assert.AreEqual(Server.LocalClientId, ownershipChangeSent.Connection.Id);
|
||||
Assert.AreEqual(FastBufferWriter.GetWriteSize<ChangeOwnershipMessage>() + FastBufferWriter.GetWriteSize<MessageHeader>(), ownershipChangeSent.BytesCount);
|
||||
Assert.AreEqual(FastBufferWriter.GetWriteSize<ChangeOwnershipMessage>() + k_MessageHeaderSize, ownershipChangeSent.BytesCount);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics
|
||||
{
|
||||
internal class ServerLogsMetricTests : SingleClientMetricTestBase
|
||||
{
|
||||
private static readonly int k_ServerLogSentMessageOverhead = 2 + FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
// Header is dynamically sized due to packing, will be 2 bytes for all test messages.
|
||||
private const int k_MessageHeaderSize = 2;
|
||||
private static readonly int k_ServerLogSentMessageOverhead = 2 + k_MessageHeaderSize;
|
||||
private static readonly int k_ServerLogReceivedMessageOverhead = 2;
|
||||
|
||||
[UnityTest]
|
||||
|
||||
@@ -13,7 +13,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics
|
||||
{
|
||||
internal class TransportBytesMetricsTests : SingleClientMetricTestBase
|
||||
{
|
||||
static readonly long MessageOverhead = 8 + FastBufferWriter.GetWriteSize<BatchHeader>() + FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
// Header is dynamically sized due to packing, will be 2 bytes for all test messages.
|
||||
private const int k_MessageHeaderSize = 2;
|
||||
static readonly long MessageOverhead = 8 + FastBufferWriter.GetWriteSize<BatchHeader>() + k_MessageHeaderSize;
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TrackTotalNumberOfBytesSent()
|
||||
|
||||
@@ -450,7 +450,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
|
||||
if (!waitResult.Result)
|
||||
{
|
||||
throw new Exception();
|
||||
Assert.Fail("Predicate condition failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
37
Tests/Runtime/NestedNetworkManagerTests.cs
Normal file
37
Tests/Runtime/NestedNetworkManagerTests.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using Unity.Netcode;
|
||||
using Unity.Netcode.RuntimeTests;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace TestProject.RuntimeTests
|
||||
{
|
||||
public class NestedNetworkManagerTests
|
||||
{
|
||||
[UnityTest]
|
||||
public IEnumerator CheckNestedNetworkManager()
|
||||
{
|
||||
var parent = new GameObject("ParentObject");
|
||||
var networkManagerObject = new GameObject(nameof(CheckNestedNetworkManager));
|
||||
|
||||
// Make our NetworkManager's GameObject nested
|
||||
networkManagerObject.transform.parent = parent.transform;
|
||||
|
||||
// Pre-generate the error message we are expecting to see
|
||||
var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform);
|
||||
var transport = networkManagerObject.AddComponent<SIPTransport>();
|
||||
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
||||
networkManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
|
||||
// Trap for the nested NetworkManager exception
|
||||
LogAssert.Expect(LogType.Error, messageToCheck);
|
||||
|
||||
yield return new WaitForSeconds(0.02f);
|
||||
|
||||
// Clean up
|
||||
Object.Destroy(parent);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Tests/Runtime/NestedNetworkManagerTests.cs.meta
Normal file
11
Tests/Runtime/NestedNetworkManagerTests.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 738f6d8fc9319fe42a986c2f43989642
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -103,6 +103,8 @@ namespace Unity.Netcode.RuntimeTests
|
||||
var serverNetVarCount = serverNetVarsToUpdate.Count;
|
||||
|
||||
yield return new WaitForSeconds(0); // wait a frame to make sure spawn is done
|
||||
// todo: with Snapshot spawns enabled and the current race condition, the following line is needed:
|
||||
// yield return new WaitForSeconds(0.2f); // wait a bit to fix the spawn/update race condition
|
||||
|
||||
foreach (var netVar in serverNetVarsToUpdate)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
using System.Collections;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that check OnNetworkDespawn being invoked
|
||||
/// </summary>
|
||||
public class NetworkObjectOnNetworkDespawnTests
|
||||
{
|
||||
private NetworkManager m_ServerHost;
|
||||
private NetworkManager[] m_Clients;
|
||||
|
||||
private GameObject m_ObjectToSpawn;
|
||||
private NetworkObject m_NetworkObject;
|
||||
|
||||
internal class OnNetworkDespawnTestComponent : NetworkBehaviour
|
||||
{
|
||||
public bool OnNetworkDespawnCalled { get; internal set; }
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
OnNetworkDespawnCalled = false;
|
||||
base.OnNetworkSpawn();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
OnNetworkDespawnCalled = true;
|
||||
base.OnNetworkDespawn();
|
||||
}
|
||||
}
|
||||
|
||||
[UnitySetUp]
|
||||
public IEnumerator Setup()
|
||||
{
|
||||
Assert.IsTrue(MultiInstanceHelpers.Create(1, out m_ServerHost, out m_Clients));
|
||||
|
||||
m_ObjectToSpawn = new GameObject();
|
||||
m_NetworkObject = m_ObjectToSpawn.AddComponent<NetworkObject>();
|
||||
m_ObjectToSpawn.AddComponent<OnNetworkDespawnTestComponent>();
|
||||
|
||||
// Make it a prefab
|
||||
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_NetworkObject);
|
||||
|
||||
var networkPrefab = new NetworkPrefab();
|
||||
networkPrefab.Prefab = m_ObjectToSpawn;
|
||||
m_ServerHost.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
|
||||
|
||||
foreach (var client in m_Clients)
|
||||
{
|
||||
client.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTearDown]
|
||||
public IEnumerator Teardown()
|
||||
{
|
||||
// Shutdown and clean up both of our NetworkManager instances
|
||||
if (m_ObjectToSpawn)
|
||||
{
|
||||
Object.Destroy(m_ObjectToSpawn);
|
||||
m_ObjectToSpawn = null;
|
||||
}
|
||||
MultiInstanceHelpers.Destroy();
|
||||
yield return null;
|
||||
}
|
||||
|
||||
public enum InstanceType
|
||||
{
|
||||
Server,
|
||||
Host,
|
||||
Client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that a spawned NetworkObject's associated NetworkBehaviours will have
|
||||
/// their OnNetworkDespawn invoked during NetworkManager shutdown.
|
||||
/// </summary>
|
||||
[UnityTest]
|
||||
public IEnumerator TestNetworkObjectDespawnOnShutdown([Values(InstanceType.Server, InstanceType.Host, InstanceType.Client)] InstanceType despawnCheck)
|
||||
{
|
||||
var useHost = despawnCheck == InstanceType.Server ? false : true;
|
||||
var networkManager = despawnCheck == InstanceType.Host || despawnCheck == InstanceType.Server ? m_ServerHost : m_Clients[0];
|
||||
|
||||
// Start the instances
|
||||
if (!MultiInstanceHelpers.Start(useHost, m_ServerHost, m_Clients))
|
||||
{
|
||||
Debug.LogError("Failed to start instances");
|
||||
Assert.Fail("Failed to start instances");
|
||||
}
|
||||
|
||||
// [Client-Side] Wait for a connection to the server
|
||||
yield return MultiInstanceHelpers.WaitForClientsConnected(m_Clients, null, 512);
|
||||
|
||||
// [Host-Server-Side] Check to make sure all clients are connected
|
||||
var clientCount = useHost ? m_Clients.Length + 1 : m_Clients.Length;
|
||||
yield return MultiInstanceHelpers.WaitForClientsConnectedToServer(m_ServerHost, clientCount, null, 512);
|
||||
|
||||
// Spawn the test object
|
||||
var spawnedObject = Object.Instantiate(m_NetworkObject);
|
||||
var spawnedNetworkObject = spawnedObject.GetComponent<NetworkObject>();
|
||||
spawnedNetworkObject.NetworkManagerOwner = m_ServerHost;
|
||||
spawnedNetworkObject.Spawn(true);
|
||||
|
||||
// Get the spawned object relative to which NetworkManager instance we are testing.
|
||||
var relativeSpawnedObject = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
|
||||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.GetComponent<OnNetworkDespawnTestComponent>() != null), networkManager, relativeSpawnedObject));
|
||||
var onNetworkDespawnTestComponent = relativeSpawnedObject.Result.GetComponent<OnNetworkDespawnTestComponent>();
|
||||
|
||||
// Confirm it is not set before shutting down the NetworkManager
|
||||
Assert.IsFalse(onNetworkDespawnTestComponent.OnNetworkDespawnCalled);
|
||||
|
||||
// Shutdown the NetworkManager instance we are testing.
|
||||
networkManager.Shutdown();
|
||||
|
||||
// Since shutdown is now delayed until the post frame update
|
||||
// just wait 2 frames before checking to see if OnNetworkDespawnCalled is true
|
||||
var currentFrame = Time.frameCount + 2;
|
||||
yield return new WaitUntil(() => Time.frameCount <= currentFrame);
|
||||
|
||||
// Confirm that OnNetworkDespawn is invoked after shutdown
|
||||
Assert.IsTrue(onNetworkDespawnTestComponent.OnNetworkDespawnCalled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93f8ca7aa8b616746a1c15592830b047
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -115,6 +115,9 @@ namespace Unity.Netcode.RuntimeTests
|
||||
Assert.That(clientNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(dummyNetworkObjectId));
|
||||
}
|
||||
|
||||
// Verifies that removing the ownership when the default (server) is already set does not cause
|
||||
// a Key Not Found Exception
|
||||
m_ServerNetworkManager.SpawnManager.RemoveOwnership(dummyNetworkObject);
|
||||
|
||||
var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[dummyNetworkObjectId];
|
||||
var clientObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[dummyNetworkObjectId];
|
||||
|
||||
@@ -11,9 +11,19 @@ namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
public uint SomeInt;
|
||||
public bool SomeBool;
|
||||
public static bool NetworkSerializeCalledOnWrite;
|
||||
public static bool NetworkSerializeCalledOnRead;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
if (serializer.IsReader)
|
||||
{
|
||||
NetworkSerializeCalledOnRead = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkSerializeCalledOnWrite = true;
|
||||
}
|
||||
serializer.SerializeValue(ref SomeInt);
|
||||
serializer.SerializeValue(ref SomeBool);
|
||||
}
|
||||
@@ -214,8 +224,8 @@ namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
return m_Player1OnServer.TheList.Count == 1 &&
|
||||
m_Player1OnClient1.TheList.Count == 1 &&
|
||||
m_Player1OnServer.TheList.Contains(k_TestKey1) &&
|
||||
m_Player1OnClient1.TheList.Contains(k_TestKey1);
|
||||
m_Player1OnServer.TheList.Contains(k_TestVal1) &&
|
||||
m_Player1OnClient1.TheList.Contains(k_TestVal1);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -318,6 +328,58 @@ namespace Unity.Netcode.RuntimeTests
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator NetworkListValueUpdate([Values(true, false)] bool useHost)
|
||||
{
|
||||
m_TestWithHost = useHost;
|
||||
yield return MultiInstanceHelpers.RunAndWaitForCondition(
|
||||
() =>
|
||||
{
|
||||
m_Player1OnServer.TheList.Add(k_TestVal1);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
return m_Player1OnServer.TheList.Count == 1 &&
|
||||
m_Player1OnClient1.TheList.Count == 1 &&
|
||||
m_Player1OnServer.TheList[0] == k_TestVal1 &&
|
||||
m_Player1OnClient1.TheList[0] == k_TestVal1;
|
||||
}
|
||||
);
|
||||
|
||||
var testSucceeded = false;
|
||||
|
||||
void TestValueUpdatedCallback(NetworkListEvent<int> changedEvent)
|
||||
{
|
||||
testSucceeded = changedEvent.PreviousValue == k_TestVal1 &&
|
||||
changedEvent.Value == k_TestVal3;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
yield return MultiInstanceHelpers.RunAndWaitForCondition(
|
||||
() =>
|
||||
{
|
||||
m_Player1OnServer.TheList[0] = k_TestVal3;
|
||||
m_Player1OnClient1.TheList.OnListChanged += TestValueUpdatedCallback;
|
||||
},
|
||||
() =>
|
||||
{
|
||||
return m_Player1OnServer.TheList.Count == 1 &&
|
||||
m_Player1OnClient1.TheList.Count == 1 &&
|
||||
m_Player1OnServer.TheList[0] == k_TestVal3 &&
|
||||
m_Player1OnClient1.TheList[0] == k_TestVal3;
|
||||
}
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_Player1OnClient1.TheList.OnListChanged -= TestValueUpdatedCallback;
|
||||
}
|
||||
|
||||
Assert.That(testSucceeded);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NetworkListIEnumerator([Values(true, false)] bool useHost)
|
||||
{
|
||||
@@ -409,6 +471,28 @@ namespace Unity.Netcode.RuntimeTests
|
||||
);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestINetworkSerializableCallsNetworkSerialize([Values(true, false)] bool useHost)
|
||||
{
|
||||
m_TestWithHost = useHost;
|
||||
yield return MultiInstanceHelpers.RunAndWaitForCondition(
|
||||
() =>
|
||||
{
|
||||
TestStruct.NetworkSerializeCalledOnWrite = false;
|
||||
TestStruct.NetworkSerializeCalledOnRead = false;
|
||||
m_Player1OnServer.TheStruct.Value =
|
||||
new TestStruct() { SomeInt = k_TestUInt, SomeBool = false };
|
||||
m_Player1OnServer.TheStruct.SetDirty(true);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
return
|
||||
TestStruct.NetworkSerializeCalledOnWrite &&
|
||||
TestStruct.NetworkSerializeCalledOnRead;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[UnityTearDown]
|
||||
public override IEnumerator Teardown()
|
||||
{
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace Unity.Netcode.RuntimeTests.Physics
|
||||
|
||||
yield return NetworkRigidbodyTestBase.WaitForFrames(5);
|
||||
|
||||
// This should equal Kinematic
|
||||
Assert.IsTrue(serverPlayer.GetComponent<Rigidbody2D>().isKinematic == Kinematic);
|
||||
|
||||
yield return NetworkRigidbodyTestBase.WaitForFrames(5);
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in conjunction with the RpcQueueTest to validate:
|
||||
/// - Sending and Receiving pipeline to validate that both sending and receiving pipelines are functioning properly.
|
||||
/// - Usage of the ServerRpcParams.Send.UpdateStage and ClientRpcParams.Send.UpdateStage functionality.
|
||||
/// - Rpcs receive will be invoked at the appropriate NetworkUpdateStage.
|
||||
/// </summary>
|
||||
public class RpcPipelineTestComponent : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the external RPCQueueTest to begin testing or stop it
|
||||
/// </summary>
|
||||
public bool PingSelfEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// How many times will we iterate through the various NetworkUpdateStage values?
|
||||
/// (defaults to 2)
|
||||
/// </summary>
|
||||
public int MaxIterations = 2;
|
||||
|
||||
// Start is called before the first frame update
|
||||
private void Start()
|
||||
{
|
||||
m_MaxStagesSent = Enum.GetValues(typeof(NetworkUpdateStage)).Length * MaxIterations;
|
||||
|
||||
//Start out with this being true (for first sequence)
|
||||
m_ClientReceivedRpc = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if we have iterated over more than our maximum stages we want to test
|
||||
/// </summary>
|
||||
/// <returns>true or false (did we exceed the max iterations or not?)</returns>
|
||||
public bool ExceededMaxIterations()
|
||||
{
|
||||
if (m_StagesSent.Count > m_MaxStagesSent && m_MaxStagesSent > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns back whether the test has completed the total number of iterations
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsTestComplete()
|
||||
{
|
||||
if (m_Counter >= MaxIterations)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool m_ClientReceivedRpc;
|
||||
private int m_Counter = 0;
|
||||
private int m_MaxStagesSent = 0;
|
||||
private ServerRpcParams m_ServerParams;
|
||||
private ClientRpcParams m_ClientParams;
|
||||
private NetworkUpdateStage m_LastUpdateStage;
|
||||
|
||||
// Update is called once per frame
|
||||
private void Update()
|
||||
{
|
||||
if (NetworkManager.Singleton.IsListening && PingSelfEnabled && m_ClientReceivedRpc)
|
||||
{
|
||||
//Reset this for the next sequence of rpcs
|
||||
m_ClientReceivedRpc = false;
|
||||
|
||||
//As long as testing isn't completed, keep testing
|
||||
if (!IsTestComplete())
|
||||
{
|
||||
PingMySelfServerRpc(m_StagesSent.Count, m_ServerParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly List<NetworkUpdateStage> m_ServerStagesReceived = new List<NetworkUpdateStage>();
|
||||
private readonly List<NetworkUpdateStage> m_ClientStagesReceived = new List<NetworkUpdateStage>();
|
||||
private readonly List<NetworkUpdateStage> m_StagesSent = new List<NetworkUpdateStage>();
|
||||
|
||||
/// <summary>
|
||||
/// Assures all update stages were in alginment with one another
|
||||
/// </summary>
|
||||
/// <returns>true or false</returns>
|
||||
public bool ValidateUpdateStages()
|
||||
{
|
||||
var validated = false;
|
||||
if (m_ServerStagesReceived.Count == m_ClientStagesReceived.Count && m_ClientStagesReceived.Count == m_StagesSent.Count)
|
||||
{
|
||||
for (int i = 0; i < m_StagesSent.Count; i++)
|
||||
{
|
||||
var currentStage = m_StagesSent[i];
|
||||
if (m_ServerStagesReceived[i] != currentStage)
|
||||
{
|
||||
Debug.Log($"ServerRpc Update Stage ({m_ServerStagesReceived[i]}) is not equal to the current update stage ({currentStage})");
|
||||
|
||||
return validated;
|
||||
}
|
||||
|
||||
if (m_ClientStagesReceived[i] != currentStage)
|
||||
{
|
||||
Debug.Log($"ClientRpc Update Stage ({m_ClientStagesReceived[i]}) is not equal to the current update stage ({currentStage})");
|
||||
|
||||
return validated;
|
||||
}
|
||||
}
|
||||
|
||||
validated = true;
|
||||
}
|
||||
|
||||
return validated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server side RPC for testing
|
||||
/// </summary>
|
||||
/// <param name="parameters">server rpc parameters</param>
|
||||
[ServerRpc]
|
||||
private void PingMySelfServerRpc(int currentCount, ServerRpcParams parameters = default)
|
||||
{
|
||||
Debug.Log($"{nameof(PingMySelfServerRpc)}: [HostClient][ServerRpc][{currentCount}] invoked.");
|
||||
|
||||
PingMySelfClientRpc(currentCount, m_ClientParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client Side RPC called by PingMySelfServerRPC to validate both Client->Server and Server-Client pipeline is working
|
||||
/// </summary>
|
||||
/// <param name="parameters">client rpc parameters</param>
|
||||
[ClientRpc]
|
||||
private void PingMySelfClientRpc(int currentCount, ClientRpcParams parameters = default)
|
||||
{
|
||||
Debug.Log($"{nameof(PingMySelfClientRpc)}: [HostServer][ClientRpc][{currentCount}] invoked. (previous output line should confirm this)");
|
||||
|
||||
m_ClientReceivedRpc = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@ namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
public class RpcTestNB : NetworkBehaviour
|
||||
{
|
||||
public event Action OnServer_Rpc;
|
||||
public event Action<ulong, ServerRpcParams> OnServer_Rpc;
|
||||
public event Action OnClient_Rpc;
|
||||
|
||||
[ServerRpc]
|
||||
public void MyServerRpc()
|
||||
public void MyServerRpc(ulong clientId, ServerRpcParams param = default)
|
||||
{
|
||||
OnServer_Rpc();
|
||||
OnServer_Rpc(clientId, param);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
@@ -42,11 +42,12 @@ namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
|
||||
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
|
||||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));
|
||||
var clientId = m_ClientNetworkManagers[0].LocalClientId;
|
||||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == clientId), m_ServerNetworkManager, serverClientPlayerResult));
|
||||
|
||||
// This is the *CLIENT VERSION* of the *CLIENT PLAYER*
|
||||
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
|
||||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
|
||||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == clientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
|
||||
|
||||
// Setup state
|
||||
bool hasReceivedServerRpc = false;
|
||||
@@ -59,15 +60,16 @@ namespace Unity.Netcode.RuntimeTests
|
||||
hasReceivedClientRpcRemotely = true;
|
||||
};
|
||||
|
||||
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += () =>
|
||||
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += (clientId, param) =>
|
||||
{
|
||||
// The RPC invoked locally. (Weaver failure?)
|
||||
Assert.Fail("ServerRpc invoked locally. Weaver failure?");
|
||||
};
|
||||
|
||||
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += () =>
|
||||
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += (clientId, param) =>
|
||||
{
|
||||
Debug.Log("ServerRpc received on server object");
|
||||
Assert.True(param.Receive.SenderClientId == clientId);
|
||||
hasReceivedServerRpc = true;
|
||||
};
|
||||
|
||||
@@ -79,7 +81,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
};
|
||||
|
||||
// Send ServerRpc
|
||||
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().MyServerRpc();
|
||||
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().MyServerRpc(clientId);
|
||||
|
||||
// Send ClientRpc
|
||||
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().MyClientRpc();
|
||||
|
||||
73
Tests/Runtime/StopStartRuntimeTests.cs
Normal file
73
Tests/Runtime/StopStartRuntimeTests.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.Collections;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
public class StopStartRuntimeTests
|
||||
{
|
||||
[UnityTest]
|
||||
public IEnumerator WhenShuttingDownAndRestarting_SDKRestartsSuccessfullyAndStaysRunning()
|
||||
{ // create server and client instances
|
||||
MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// create prefab
|
||||
var gameObject = new GameObject("PlayerObject");
|
||||
var networkObject = gameObject.AddComponent<NetworkObject>();
|
||||
networkObject.DontDestroyWithOwner = true;
|
||||
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject);
|
||||
|
||||
server.NetworkConfig.PlayerPrefab = gameObject;
|
||||
|
||||
for (int i = 0; i < clients.Length; i++)
|
||||
{
|
||||
clients[i].NetworkConfig.PlayerPrefab = gameObject;
|
||||
}
|
||||
|
||||
// start server and connect clients
|
||||
MultiInstanceHelpers.Start(false, server, clients);
|
||||
|
||||
// wait for connection on client side
|
||||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients));
|
||||
|
||||
// wait for connection on server side
|
||||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(server));
|
||||
|
||||
// shutdown the server
|
||||
server.Shutdown();
|
||||
|
||||
// wait 1 frame because shutdowns are delayed
|
||||
var nextFrameNumber = Time.frameCount + 1;
|
||||
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
|
||||
|
||||
// Verify the shutdown occurred
|
||||
Assert.IsFalse(server.IsServer);
|
||||
Assert.IsFalse(server.IsListening);
|
||||
Assert.IsFalse(server.IsHost);
|
||||
Assert.IsFalse(server.IsClient);
|
||||
|
||||
server.StartServer();
|
||||
// Verify the server started
|
||||
Assert.IsTrue(server.IsServer);
|
||||
Assert.IsTrue(server.IsListening);
|
||||
|
||||
// Wait several frames
|
||||
nextFrameNumber = Time.frameCount + 10;
|
||||
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
|
||||
|
||||
// Verify the server is still running
|
||||
Assert.IsTrue(server.IsServer);
|
||||
Assert.IsTrue(server.IsListening);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// cleanup
|
||||
MultiInstanceHelpers.Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Tests/Runtime/StopStartRuntimeTests.cs.meta
Normal file
11
Tests/Runtime/StopStartRuntimeTests.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97a5298e33ee4d32be46ce84fecdcd06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -56,8 +56,8 @@ namespace Unity.Netcode.RuntimeTests
|
||||
var networkManagers = MultiInstanceHelpers.NetworkManagerInstances.ToArray();
|
||||
|
||||
var server = networkManagers.First(t => t.IsServer);
|
||||
var firstClient = networkManagers.First(t => t.IsClient);
|
||||
var secondClient = networkManagers.Last(t => t.IsClient);
|
||||
var firstClient = networkManagers.First(t => !t.IsServer);
|
||||
var secondClient = networkManagers.Last(t => !t.IsServer);
|
||||
|
||||
Assert.AreNotEqual(firstClient, secondClient);
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
s_Server = null;
|
||||
m_Peers.Remove(ServerClientId);
|
||||
m_LocalConnection = null;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
||||
11
package.json
11
package.json
@@ -2,21 +2,22 @@
|
||||
"name": "com.unity.netcode.gameobjects",
|
||||
"displayName": "Netcode for GameObjects",
|
||||
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
|
||||
"version": "1.0.0-pre.3",
|
||||
"version": "1.0.0-pre.5",
|
||||
"unity": "2020.3",
|
||||
"dependencies": {
|
||||
"com.unity.modules.ai": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.physics": "1.0.0",
|
||||
"com.unity.modules.physics2d": "1.0.0",
|
||||
"com.unity.nuget.mono-cecil": "1.10.1",
|
||||
"com.unity.collections": "1.0.0-pre.5"
|
||||
"com.unity.collections": "1.1.0"
|
||||
},
|
||||
"upmCi": {
|
||||
"footprint": "883b3567bbb5155b7d06ef3c2cac755efa58a235"
|
||||
"footprint": "770504b1f691c766a300b616c1d8106335265f3c"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
|
||||
"type": "git",
|
||||
"revision": "3e4df72dadeea8bd622da2824e30541910c79d3d"
|
||||
"revision": "d1f990d97b80d49ce12fce7357cbd5f6b794ce01"
|
||||
},
|
||||
"samples": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user