From 36d07fad5e18b02412dc27f39a0f1fe7a20fe024 Mon Sep 17 00:00:00 2001
From: Unity Technologies <@unity>
Date: Mon, 4 Jan 2021 00:00:00 +0000
Subject: [PATCH] com.unity.netcode.gameobjects@1.0.0-pre.4 The format is based
on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.0.0-pre.4] - 2021-01-04
### Added
- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)
### Removed
- Removed `com.unity.modules.ai` package dependency (#1565)
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
### Fixed
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)
### Changed
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
- Updated com.unity.collections to 1.1.0 (#1451)
---
CHANGELOG.md | 28 ++
Components/NetworkRigidbody2D.cs | 3 +-
Components/NetworkTransform.cs | 2 +-
Editor/CodeGen/CodeGenHelpers.cs | 22 ++
Editor/CodeGen/INetworkSerializableILPP.cs | 167 ++++++++
.../CodeGen/INetworkSerializableILPP.cs.meta | 3 +
Editor/CodeGen/NetworkBehaviourILPP.cs | 50 ++-
Editor/CodeGen/RuntimeAccessModifiersILPP.cs | 14 +
Editor/NetworkManagerEditor.cs | 2 +-
Runtime/Collections.meta | 8 -
Runtime/Collections/FixedQueue.cs | 77 ----
Runtime/Configuration/NetworkConfig.cs | 8 +-
Runtime/Configuration/NetworkConstants.cs | 2 +-
Runtime/Core/NetworkBehaviour.cs | 14 +-
Runtime/Core/NetworkManager.cs | 71 +++-
.../Core/{SnapshotRTT.cs => SnapshotRtt.cs} | 0
...napshotRTT.cs.meta => SnapshotRtt.cs.meta} | 0
Runtime/Core/SnapshotSystem.cs | 374 +++++++++++-------
Runtime/Messaging/CustomMessageManager.cs | 8 +-
Runtime/Messaging/MessageHeader.cs | 5 +-
Runtime/Messaging/Messages/NamedMessage.cs | 2 +-
.../Messaging/Messages/SnapshotDataMessage.cs | 29 +-
Runtime/Messaging/Messages/UnnamedMessage.cs | 2 +-
Runtime/Messaging/MessagingSystem.cs | 166 ++++----
Runtime/Messaging/NetworkContext.cs | 5 +
Runtime/Metrics/StreamExtensions.cs | 12 -
Runtime/NetworkVariable/NetworkVariable.cs | 55 ++-
.../NetworkVariable/NetworkVariableHelper.cs | 22 ++
.../NetworkVariableHelper.cs.meta} | 2 +-
Runtime/Reflection.meta | 8 -
Runtime/Reflection/TypeExtensions.cs | 31 --
.../SceneManagement/NetworkSceneManager.cs | 41 +-
Runtime/Serialization/FastBufferWriter.cs | 4 +-
Runtime/Spawning/NetworkSpawnManager.cs | 122 ++++--
.../Editor/Messaging/MessageReceivingTests.cs | 29 +-
Tests/Editor/Messaging/MessageSendingTests.cs | 31 +-
Tests/Editor/SnapshotRttTests.cs | 4 +-
Tests/Editor/SnapshotTests.cs | 372 +++++++++++++++++
.../Editor/SnapshotTests.cs.meta | 2 +-
Tests/Editor/StartStopTests.cs | 15 +-
.../com.unity.netcode.editortests.asmdef | 1 +
.../Runtime/Metrics/MessagingMetricsTests.cs | 6 +-
.../Metrics/OwnershipChangeMetricsTests.cs | 4 +-
.../Runtime/Metrics/ServerLogsMetricTests.cs | 4 +-
.../Metrics/TransportBytesMetricsTests.cs | 4 +-
Tests/Runtime/MultiInstanceHelpers.cs | 2 +-
Tests/Runtime/NetworkBehaviourUpdaterTests.cs | 2 +
.../NetworkObjectOnNetworkDespawnTests.cs | 132 +++++++
...NetworkObjectOnNetworkDespawnTests.cs.meta | 2 +-
.../NetworkObjectOwnershipTests.cs | 3 +
Tests/Runtime/NetworkVariableTests.cs | 32 ++
.../Runtime/Physics/NetworkRigidbody2DTest.cs | 1 +
Tests/Runtime/RpcPipelineTestComponent.cs | 148 -------
Tests/Runtime/RpcTests.cs | 18 +-
Tests/Runtime/StopStartRuntimeTests.cs | 73 ++++
....cs.meta => StopStartRuntimeTests.cs.meta} | 2 +-
Tests/Runtime/Timing/TimeMultiInstanceTest.cs | 4 +-
Tests/Runtime/Transport/SIPTransport.cs | 1 +
package.json | 11 +-
59 files changed, 1583 insertions(+), 679 deletions(-)
create mode 100644 Editor/CodeGen/INetworkSerializableILPP.cs
create mode 100644 Editor/CodeGen/INetworkSerializableILPP.cs.meta
delete mode 100644 Runtime/Collections.meta
delete mode 100644 Runtime/Collections/FixedQueue.cs
rename Runtime/Core/{SnapshotRTT.cs => SnapshotRtt.cs} (100%)
rename Runtime/Core/{SnapshotRTT.cs.meta => SnapshotRtt.cs.meta} (100%)
delete mode 100644 Runtime/Metrics/StreamExtensions.cs
create mode 100644 Runtime/NetworkVariable/NetworkVariableHelper.cs
rename Runtime/{Reflection/TypeExtensions.cs.meta => NetworkVariable/NetworkVariableHelper.cs.meta} (83%)
delete mode 100644 Runtime/Reflection.meta
delete mode 100644 Runtime/Reflection/TypeExtensions.cs
create mode 100644 Tests/Editor/SnapshotTests.cs
rename Runtime/Metrics/StreamExtensions.cs.meta => Tests/Editor/SnapshotTests.cs.meta (83%)
create mode 100644 Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs
rename Runtime/Collections/FixedQueue.cs.meta => Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs.meta (83%)
delete mode 100644 Tests/Runtime/RpcPipelineTestComponent.cs
create mode 100644 Tests/Runtime/StopStartRuntimeTests.cs
rename Tests/Runtime/{RpcPipelineTestComponent.cs.meta => StopStartRuntimeTests.cs.meta} (83%)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b7462ed..5b925fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,34 @@ 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.4] - 2021-01-04
+
+### Added
+
+- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)
+
+### Removed
+
+- Removed `com.unity.modules.ai` package dependency (#1565)
+- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
+
+### Fixed
+- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
+- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
+- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
+- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
+- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
+- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
+- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
+- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
+- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
+- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
+- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)
+
+### Changed
+- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
+- Updated com.unity.collections to 1.1.0 (#1451)
+
## [1.0.0-pre.3] - 2021-10-22
### Added
diff --git a/Components/NetworkRigidbody2D.cs b/Components/NetworkRigidbody2D.cs
index e0997da..b9c809d 100644
--- a/Components/NetworkRigidbody2D.cs
+++ b/Components/NetworkRigidbody2D.cs
@@ -32,7 +32,7 @@ private void Awake()
private void FixedUpdate()
{
- if (NetworkManager.IsListening)
+ if (IsSpawned)
{
if (HasAuthority != m_IsAuthority)
{
@@ -74,7 +74,6 @@ public override void OnNetworkSpawn()
///
public override void OnNetworkDespawn()
{
- m_IsAuthority = false;
UpdateRigidbodyKinematicMode();
}
}
diff --git a/Components/NetworkTransform.cs b/Components/NetworkTransform.cs
index 831d1e2..e7a4e79 100644
--- a/Components/NetworkTransform.cs
+++ b/Components/NetworkTransform.cs
@@ -807,7 +807,7 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool
// conditional to users only making transform update changes in FixedUpdate.
protected virtual void Update()
{
- if (!NetworkObject.IsSpawned)
+ if (!IsSpawned)
{
return;
}
diff --git a/Editor/CodeGen/CodeGenHelpers.cs b/Editor/CodeGen/CodeGenHelpers.cs
index 390d190..6e5e1ff 100644
--- a/Editor/CodeGen/CodeGenHelpers.cs
+++ b/Editor/CodeGen/CodeGenHelpers.cs
@@ -264,6 +264,28 @@ public static void AddError(this List diagnostics, SequencePo
});
}
+ public static void AddWarning(this List diagnostics, string message)
+ {
+ diagnostics.AddWarning((SequencePoint)null, message);
+ }
+
+ public static void AddWarning(this List diagnostics, MethodDefinition methodDefinition, string message)
+ {
+ diagnostics.AddWarning(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message);
+ }
+
+ public static void AddWarning(this List 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
diff --git a/Editor/CodeGen/INetworkSerializableILPP.cs b/Editor/CodeGen/INetworkSerializableILPP.cs
new file mode 100644
index 0000000..49af82b
--- /dev/null
+++ b/Editor/CodeGen/INetworkSerializableILPP.cs
@@ -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 m_Diagnostics = new List();
+
+ 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 networkSerializableTypes)
+ {
+ foreach (var typeDefinition in assembly.MainModule.Types)
+ {
+ if (typeDefinition.FullName == "")
+ {
+ var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
+
+ var processor = staticCtorMethodDef.Body.GetILProcessor();
+
+ var instructions = new List();
+
+ 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;
+ }
+ }
+ }
+ }
+}
diff --git a/Editor/CodeGen/INetworkSerializableILPP.cs.meta b/Editor/CodeGen/INetworkSerializableILPP.cs.meta
new file mode 100644
index 0000000..7043beb
--- /dev/null
+++ b/Editor/CodeGen/INetworkSerializableILPP.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 64a0c1e708fa46a389d64e7b4708e6c7
+timeCreated: 1635535237
\ No newline at end of file
diff --git a/Editor/CodeGen/NetworkBehaviourILPP.cs b/Editor/CodeGen/NetworkBehaviourILPP.cs
index d49d48c..f6de9f1 100644
--- a/Editor/CodeGen/NetworkBehaviourILPP.cs
+++ b/Editor/CodeGen/NetworkBehaviourILPP.cs
@@ -312,7 +312,11 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
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,21 +579,28 @@ private MethodReference GetFastBufferWriterWriteMethod(string name, TypeReferenc
{
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))
{
- var instanceMethod = new GenericInstanceMethod(method);
- instanceMethod.GenericArguments.Add(checkType);
- return instanceMethod;
+ meetsConstraints = false;
+ break;
}
}
+ if (meetsConstraints)
+ {
+ var instanceMethod = new GenericInstanceMethod(method);
+ instanceMethod.GenericArguments.Add(checkType);
+ return instanceMethod;
+ }
}
}
}
@@ -950,17 +961,23 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
// 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 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition
if (foundMethodRef)
{
// reader.ReadValueSafe(out localVar);
- processor.Emit(OpCodes.Ldarga, 1);
+
+ var checkParameter = methodRef.Resolve().Parameters[0];
+
+ var isExtensionMethod = methodRef.Resolve().DeclaringType != m_FastBufferReader_TypeRef.Resolve();
+ if (!isExtensionMethod || checkParameter.ParameterType.IsByReference)
+ {
+ processor.Emit(OpCodes.Ldarga, 1);
+ }
+ else
+ {
+ processor.Emit(OpCodes.Ldarg, 1);
+ }
processor.Emit(OpCodes.Ldloca, localIndex);
if (paramType == typeSystem.String)
{
diff --git a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs
index 1c3d846..17f9b50 100644
--- a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs
+++ b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs
@@ -52,6 +52,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
case nameof(NetworkBehaviour):
ProcessNetworkBehaviour(typeDefinition);
break;
+ case nameof(NetworkVariableHelper):
+ ProcessNetworkVariableHelper(typeDefinition);
+ break;
case nameof(__RpcParams):
typeDefinition.IsPublic = true;
break;
@@ -100,6 +103,17 @@ private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assem
}
}
+ 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)
diff --git a/Editor/NetworkManagerEditor.cs b/Editor/NetworkManagerEditor.cs
index 63342c1..6cb2ccd 100644
--- a/Editor/NetworkManagerEditor.cs
+++ b/Editor/NetworkManagerEditor.cs
@@ -363,7 +363,7 @@ private static void DrawInstallMultiplayerToolsTip()
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)
diff --git a/Runtime/Collections.meta b/Runtime/Collections.meta
deleted file mode 100644
index 6f36eb7..0000000
--- a/Runtime/Collections.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: f2ef964afcae91248b2298b479ed1b53
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Runtime/Collections/FixedQueue.cs b/Runtime/Collections/FixedQueue.cs
deleted file mode 100644
index 1cbb4f1..0000000
--- a/Runtime/Collections/FixedQueue.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-
-namespace Unity.Netcode
-{
- ///
- /// Queue with a fixed size
- ///
- /// The type of the queue
- public sealed class FixedQueue
- {
- private readonly T[] m_Queue;
- private int m_QueueCount = 0;
- private int m_QueueStart;
-
- ///
- /// The amount of enqueued objects
- ///
- public int Count => m_QueueCount;
-
- ///
- /// Gets the element at a given virtual index
- ///
- /// The virtual index to get the item from
- /// The element at the virtual index
- public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
-
- ///
- /// Creates a new FixedQueue with a given size
- ///
- /// The size of the queue
- public FixedQueue(int maxSize)
- {
- m_Queue = new T[maxSize];
- m_QueueStart = 0;
- }
-
- ///
- /// Enqueues an object
- ///
- ///
- ///
- 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;
- }
-
- ///
- /// Dequeues an object
- ///
- ///
- 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;
- }
-
- ///
- /// Gets the element at a given virtual index
- ///
- /// The virtual index to get the item from
- /// The element at the virtual index
- public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
- }
-}
diff --git a/Runtime/Configuration/NetworkConfig.cs b/Runtime/Configuration/NetworkConfig.cs
index d938294..ccd628f 100644
--- a/Runtime/Configuration/NetworkConfig.cs
+++ b/Runtime/Configuration/NetworkConfig.cs
@@ -141,15 +141,15 @@ public class NetworkConfig
///
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
///
- public bool UseSnapshotDelta { get; } = false;
+ public bool UseSnapshotDelta { get; internal set; } = false;
///
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
///
- public bool UseSnapshotSpawn { get; } = false;
+ public bool UseSnapshotSpawn { get; internal set; } = false;
///
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
///
- 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 @@ public ulong GetConfig(bool cache = true)
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);
diff --git a/Runtime/Configuration/NetworkConstants.cs b/Runtime/Configuration/NetworkConstants.cs
index 55facaf..b9eed28 100644
--- a/Runtime/Configuration/NetworkConstants.cs
+++ b/Runtime/Configuration/NetworkConstants.cs
@@ -5,6 +5,6 @@ namespace Unity.Netcode
///
internal static class NetworkConstants
{
- internal const string PROTOCOL_VERSION = "14.0.0";
+ internal const string PROTOCOL_VERSION = "15.0.0";
}
}
diff --git a/Runtime/Core/NetworkBehaviour.cs b/Runtime/Core/NetworkBehaviour.cs
index 0c13955..f271c4c 100644
--- a/Runtime/Core/NetworkBehaviour.cs
+++ b/Runtime/Core/NetworkBehaviour.cs
@@ -82,7 +82,8 @@ internal void __sendServerRpc(FastBufferWriter writer, uint rpcMethodId, ServerR
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 @@ internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId,
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,9 +284,13 @@ public NetworkObject NetworkObject
m_NetworkObject = GetComponentInParent();
}
- if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ if (m_NetworkObject == null || NetworkManager.Singleton == null ||
+ (NetworkManager.Singleton != null && !NetworkManager.Singleton.ShutdownInProgress))
{
- NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
+ if (NetworkLog.CurrentLogLevel < LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
+ }
}
return m_NetworkObject;
diff --git a/Runtime/Core/NetworkManager.cs b/Runtime/Core/NetworkManager.cs
index fe26534..097d9bb 100644
--- a/Runtime/Core/NetworkManager.cs
+++ b/Runtime/Core/NetworkManager.cs
@@ -73,6 +73,9 @@ public NetworkPrefabHandler PrefabHandler
}
}
+ private bool m_ShuttingDown;
+ private bool m_StopProcessingMessages;
+
private class NetworkManagerHooks : INetworkHooks
{
private NetworkManager m_NetworkManager;
@@ -116,7 +119,7 @@ public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeI
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 @@ public bool OnVerifyCanReceive(ulong senderId, Type messageType)
return false;
}
- return true;
+ return !m_NetworkManager.m_StopProcessingMessages;
}
}
@@ -333,6 +336,9 @@ public IReadOnlyList ConnectedClientsIds
///
public bool IsConnectedClient { get; internal set; }
+
+ public bool ShutdownInProgress { get { return m_ShuttingDown; } }
+
///
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
///
@@ -345,8 +351,6 @@ public IReadOnlyList ConnectedClientsIds
///
public event Action OnClientDisconnectCallback = null;
- internal void InvokeOnClientDisconnectCallback(ulong clientId) => OnClientDisconnectCallback?.Invoke(clientId);
-
///
/// The callback to invoke once the server is ready
///
@@ -556,8 +560,6 @@ private void Initialize(bool server)
SnapshotSystem = null;
}
- SnapshotSystem = new SnapshotSystem(this);
-
if (server)
{
NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
@@ -570,6 +572,8 @@ private void Initialize(bool server)
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
@@ -976,7 +980,7 @@ private void OnApplicationQuit()
// Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
private void OnDestroy()
{
- Shutdown();
+ ShutdownInternal();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
@@ -996,13 +1000,30 @@ private void DisconnectRemoteClient(ulong clientId)
/// Globally shuts down the library.
/// Disconnects clients if connected and stops server if running.
///
- public void Shutdown()
+ ///
+ /// 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.
+ ///
+ 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 +1096,15 @@ public void Shutdown()
MessagingSystem = null;
}
- NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
+ if (NetworkConfig?.NetworkTransport != null)
+ {
+ NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
+ }
if (SpawnManager != null)
{
- SpawnManager.DestroyNonSceneObjects();
+ SpawnManager.CleanupAllTriggers();
+ SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
@@ -1114,6 +1139,8 @@ public void Shutdown()
m_TransportIdToClientIdMap.Clear();
IsListening = false;
+ m_ShuttingDown = false;
+ m_StopProcessingMessages = false;
}
// INetworkUpdateSystem
@@ -1167,6 +1194,11 @@ private void OnNetworkPreUpdate()
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)
@@ -1183,9 +1215,18 @@ private void OnNetworkPreUpdate()
private void OnNetworkPostLateUpdate()
{
- MessagingSystem.ProcessSendQueues();
- NetworkMetrics.DispatchFrame();
+
+ if (!m_ShuttingDown || !m_StopProcessingMessages)
+ {
+ MessagingSystem.ProcessSendQueues();
+ NetworkMetrics.DispatchFrame();
+ }
SpawnManager.CleanupStaleTriggers();
+
+ if (m_ShuttingDown)
+ {
+ ShutdownInternal();
+ }
}
///
@@ -1312,6 +1353,8 @@ private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, A
#endif
clientId = TransportIdToClientId(clientId);
+ OnClientDisconnectCallback?.Invoke(clientId);
+
m_TransportIdToClientIdMap.Remove(transportId);
m_ClientIdToTransportIdMap.Remove(clientId);
@@ -1328,9 +1371,6 @@ private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, A
{
Shutdown();
}
-
- OnClientDisconnectCallback?.Invoke(clientId);
-
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End();
#endif
@@ -1596,6 +1636,7 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint?
}
else // Server just adds itself as an observer to all spawned NetworkObjects
{
+ LocalClient = client;
SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
InvokeOnClientConnectedCallback(ownerClientId);
}
diff --git a/Runtime/Core/SnapshotRTT.cs b/Runtime/Core/SnapshotRtt.cs
similarity index 100%
rename from Runtime/Core/SnapshotRTT.cs
rename to Runtime/Core/SnapshotRtt.cs
diff --git a/Runtime/Core/SnapshotRTT.cs.meta b/Runtime/Core/SnapshotRtt.cs.meta
similarity index 100%
rename from Runtime/Core/SnapshotRTT.cs.meta
rename to Runtime/Core/SnapshotRtt.cs.meta
diff --git a/Runtime/Core/SnapshotSystem.cs b/Runtime/Core/SnapshotSystem.cs
index b8fe48e..8d9a9c3 100644
--- a/Runtime/Core/SnapshotSystem.cs
+++ b/Runtime/Core/SnapshotSystem.cs
@@ -62,20 +62,63 @@ internal struct SnapshotSpawnCommand
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 SpawnAck = new Dictionary();
+
+ // list of spawn and despawns commands we sent, with sequence number
+ // need to manage acknowledgements
+ internal List SentSpawns = new List();
+ }
+
+ 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 m_ClientData = new Dictionary();
+ private Dictionary m_ConnectionRtts = new Dictionary();
+
+ 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 @@ internal class Snapshot
internal SnapshotDespawnCommand[] Despawns;
internal int NumDespawns = 0;
- internal NetworkManager NetworkManager;
-
// indexed by ObjectId
internal Dictionary TickAppliedSpawn = new Dictionary();
internal Dictionary TickAppliedDespawn = new Dictionary();
- ///
- /// Constructor
- /// Allocated a MemoryStream to be reused for this Snapshot
- ///
- 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 ConnectedClientsId { get; } = new List();
+ internal MockSendMessage MockSendMessage { get; set; }
+ internal MockSpawnObject MockSpawnObject { get; set; }
+ internal MockDespawnObject MockDespawnObject { get; set; }
internal void Clear()
{
@@ -156,15 +193,15 @@ internal List GetClientList()
List clientList;
clientList = new List();
- 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 @@ internal List GetClientList()
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 @@ internal void AddSpawn(SnapshotSpawnCommand command)
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 @@ internal void AddDespawn(SnapshotDespawnCommand command)
}
}
+ 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 @@ internal void ReadIndex(in SnapshotDataMessage message)
}
}
- 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 @@ internal void ReadSpawns(in SnapshotDataMessage message)
// 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 @@ internal void ReadSpawns(in SnapshotDataMessage message)
// 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 @@ internal void ProcessSingleAck(ushort ackSequence, ulong clientId, ClientData cl
/// The key to search for
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 @@ private NetworkVariableBase FindNetworkVar(VariableKey key)
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 SpawnAck = new Dictionary();
-
- // list of spawn and despawns commands we sent, with sequence number
- // need to manage acknowledgements
- internal List SentSpawns = new List();
- }
-
- 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 m_ClientData = new Dictionary();
- private Dictionary m_ConnectionRtts = new Dictionary();
-
- private int m_CurrentTick = NetworkTickSystem.NoTick;
///
/// Constructor
///
/// 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 @@ public void Dispose()
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 @@ private void SendSnapshot(ulong clientId)
{
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 @@ private void SendSnapshot(ulong clientId)
WriteIndex(ref message);
WriteSpawns(ref message, clientId);
- m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, 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 @@ private void WriteSpawns(ref SnapshotDataMessage message, ulong clientId)
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(m_Snapshot.NumSpawns, Allocator.TempJob);
- message.Despawns = new NativeList(m_Snapshot.NumDespawns, Allocator.TempJob);
+ message.Spawns = new NativeList(NumSpawns, Collections.Allocator.TempJob);
+ message.Despawns = new NativeList(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();
// 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 @@ private void WriteSpawns(ref SnapshotDataMessage message, ulong clientId)
// 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();
// 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 @@ private void WriteSpawns(ref SnapshotDataMessage message, ulong clientId)
/// The message to write the index to
private void WriteIndex(ref SnapshotDataMessage message)
{
- message.Entries = new NativeList(m_Snapshot.LastEntry, Allocator.TempJob);
- for (var i = 0; i < m_Snapshot.LastEntry; i++)
+ message.Entries = new NativeList(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 @@ private void WriteIndex(ref SnapshotDataMessage message)
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 @@ internal void Spawn(SnapshotSpawnCommand command)
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 @@ internal void Store(ulong networkObjectId, int behaviourIndex, int variableIndex
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 @@ internal void HandleSnapshot(ulong clientId, in SnapshotDataMessage message)
// 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 @@ private void DebugDisplayStore()
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 @@ private void DebugDisplayStore()
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";
diff --git a/Runtime/Messaging/CustomMessageManager.cs b/Runtime/Messaging/CustomMessageManager.cs
index 0b4e9a0..7a7938f 100644
--- a/Runtime/Messaging/CustomMessageManager.cs
+++ b/Runtime/Messaging/CustomMessageManager.cs
@@ -28,7 +28,7 @@ internal CustomMessagingManager(NetworkManager networkManager)
///
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 @@ internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader)
((UnnamedMessageDelegate)handler).Invoke(clientId, reader);
}
}
- m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + FastBufferWriter.GetWriteSize());
+ m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + serializedHeaderSize);
}
///
@@ -115,9 +115,9 @@ public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, N
private Dictionary m_MessageHandlerNameLookup32 = new Dictionary();
private Dictionary m_MessageHandlerNameLookup64 = new Dictionary();
- 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();
+ var bytesCount = reader.Length + serializedHeaderSize;
if (m_NetworkManager == null)
{
diff --git a/Runtime/Messaging/MessageHeader.cs b/Runtime/Messaging/MessageHeader.cs
index a3a9543..a7e3ef1 100644
--- a/Runtime/Messaging/MessageHeader.cs
+++ b/Runtime/Messaging/MessageHeader.cs
@@ -11,11 +11,12 @@ internal struct MessageHeader
/// unchanged - if new messages are added or messages are removed, MessageType assignments may be
/// calculated differently.
///
- public byte MessageType;
+ public uint MessageType;
///
/// The total size of the message, NOT including the header.
+ /// Stored as a uint to avoid zig-zag encoding, but capped at int.MaxValue.
///
- public ushort MessageSize;
+ public uint MessageSize;
}
}
diff --git a/Runtime/Messaging/Messages/NamedMessage.cs b/Runtime/Messaging/Messages/NamedMessage.cs
index a6bab60..2583d4f 100644
--- a/Runtime/Messaging/Messages/NamedMessage.cs
+++ b/Runtime/Messaging/Messages/NamedMessage.cs
@@ -16,7 +16,7 @@ public static void Receive(FastBufferReader reader, in NetworkContext context)
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);
}
}
}
diff --git a/Runtime/Messaging/Messages/SnapshotDataMessage.cs b/Runtime/Messaging/Messages/SnapshotDataMessage.cs
index 1d1ec8b..bc0aa94 100644
--- a/Runtime/Messaging/Messages/SnapshotDataMessage.cs
+++ b/Runtime/Messaging/Messages/SnapshotDataMessage.cs
@@ -104,7 +104,6 @@ public unsafe void Serialize(FastBufferWriter writer)
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,20 +141,32 @@ public static unsafe void Receive(FastBufferReader reader, in NetworkContext con
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)
{
- // todo: temporary hack around bug
- if (!networkManager.IsServer)
+ if (systemOwner is NetworkManager)
{
- senderId = networkManager.ServerClientId;
- }
+ var networkManager = (NetworkManager)systemOwner;
+
+ // todo: temporary hack around bug
+ if (!networkManager.IsServer)
+ {
+ senderId = networkManager.ServerClientId;
+ }
- var snapshotSystem = networkManager.SnapshotSystem;
- snapshotSystem.HandleSnapshot(senderId, this);
+ var snapshotSystem = networkManager.SnapshotSystem;
+ snapshotSystem.HandleSnapshot(senderId, this);
+ }
+ else
+ {
+ var ownerData = (Tuple)systemOwner;
+ var snapshotSystem = ownerData.Item1;
+ snapshotSystem.HandleSnapshot(ownerData.Item2, this);
+ return;
+ }
}
}
}
diff --git a/Runtime/Messaging/Messages/UnnamedMessage.cs b/Runtime/Messaging/Messages/UnnamedMessage.cs
index 9f349de..7969e88 100644
--- a/Runtime/Messaging/Messages/UnnamedMessage.cs
+++ b/Runtime/Messaging/Messages/UnnamedMessage.cs
@@ -11,7 +11,7 @@ public unsafe void Serialize(FastBufferWriter writer)
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);
}
}
}
diff --git a/Runtime/Messaging/MessagingSystem.cs b/Runtime/Messaging/MessagingSystem.cs
index 41d27a6..a74a856 100644
--- a/Runtime/Messaging/MessagingSystem.cs
+++ b/Runtime/Messaging/MessagingSystem.cs
@@ -23,6 +23,7 @@ private struct ReceiveQueueItem
public MessageHeader Header;
public ulong SenderId;
public float Timestamp;
+ public int MessageHeaderSerializedSize;
}
private struct SendQueueItem
@@ -46,27 +47,27 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
private Type[] m_ReverseTypeMap = new Type[255];
- private Dictionary m_MessageTypes = new Dictionary();
+ private Dictionary m_MessageTypes = new Dictionary();
private Dictionary> m_SendQueues = new Dictionary>();
private List m_Hooks = new List();
- 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 @@ public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvi
}
}
- public void Dispose()
+ public unsafe void Dispose()
{
if (m_Disposed)
{
@@ -113,6 +114,14 @@ public void Dispose()
{
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 @@ internal void HandleIncomingData(ulong clientId, ArraySegment data, float
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 @@ internal void HandleIncomingData(ulong clientId, ArraySegment data, float
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 @@ internal void HandleIncomingData(ulong clientId, ArraySegment data, float
// 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 @@ private bool CanReceive(ulong clientId, Type messageType)
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 @@ public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulon
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 @@ public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulon
{
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize());
}
+
var handler = m_MessageHandlers[header.MessageType];
using (reader)
{
@@ -253,11 +275,15 @@ public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulon
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,64 +342,68 @@ internal unsafe int SendMessage(in TMessageType
}
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(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize());
- using (tmpSerializer)
+
+ using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize());
+
+ message.Serialize(tmpSerializer);
+
+ using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize(), Allocator.Temp);
+
+ var header = new MessageHeader
{
- message.Serialize(tmpSerializer);
+ 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];
+ for (var i = 0; i < clientIds.Count; ++i)
+ {
+ var clientId = clientIds[i];
- if (!CanSend(clientId, typeof(TMessageType), delivery))
- {
- continue;
- }
+ if (!CanSend(clientId, typeof(TMessageType), delivery))
+ {
+ continue;
+ }
- for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
- {
- m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery);
- }
+ for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
+ {
+ m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery);
+ }
- var sendQueueItem = m_SendQueues[clientId];
- if (sendQueueItem.Length == 0)
+ var sendQueueItem = m_SendQueues[clientId];
+ if (sendQueueItem.Length == 0)
+ {
+ sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
+ maxSize));
+ sendQueueItem.GetUnsafeList()->ElementAt(0).Writer.Seek(sizeof(BatchHeader));
+ }
+ else
+ {
+ ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
+ if (lastQueueItem.NetworkDelivery != delivery ||
+ lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
+ < tmpSerializer.Length + headerSerializer.Length)
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
- sendQueueItem.GetUnsafeList()->ElementAt(0).Writer.Seek(sizeof(BatchHeader));
- }
- else
- {
- ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
- if (lastQueueItem.NetworkDelivery != delivery ||
- lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
- < tmpSerializer.Length + FastBufferWriter.GetWriteSize())
- {
- sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
- maxSize));
- sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
- }
+ sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
}
+ }
- ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
- writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + FastBufferWriter.GetWriteSize());
- var header = new MessageHeader
- {
- MessageSize = (ushort)tmpSerializer.Length,
- MessageType = m_MessageTypes[typeof(TMessageType)],
- };
+ ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
+ writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
- writeQueueItem.Writer.WriteValue(header);
- writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
- writeQueueItem.BatchHeader.BatchSize++;
- for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
- {
- m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + FastBufferWriter.GetWriteSize());
- }
+ 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 + headerSerializer.Length);
}
-
- return tmpSerializer.Length + FastBufferWriter.GetWriteSize();
}
+
+ return tmpSerializer.Length + headerSerializer.Length;
}
private struct PointerListWrapper : IReadOnlyList
@@ -461,16 +491,16 @@ internal unsafe void ProcessSendQueues()
try
{
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
+
+ for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
+ {
+ m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
+ }
}
finally
{
queueItem.Writer.Dispose();
}
-
- for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
- {
- m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
- }
}
sendQueueItem.Clear();
}
diff --git a/Runtime/Messaging/NetworkContext.cs b/Runtime/Messaging/NetworkContext.cs
index 8adaffa..f588b5c 100644
--- a/Runtime/Messaging/NetworkContext.cs
+++ b/Runtime/Messaging/NetworkContext.cs
@@ -25,5 +25,10 @@ internal ref struct NetworkContext
/// The header data that was sent with the message
///
public MessageHeader Header;
+
+ ///
+ /// The actual serialized size of the header when packed into the buffer
+ ///
+ public int SerializedHeaderSize;
}
}
diff --git a/Runtime/Metrics/StreamExtensions.cs b/Runtime/Metrics/StreamExtensions.cs
deleted file mode 100644
index 615efb5..0000000
--- a/Runtime/Metrics/StreamExtensions.cs
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/Runtime/NetworkVariable/NetworkVariable.cs b/Runtime/NetworkVariable/NetworkVariable.cs
index 013877c..f247bac 100644
--- a/Runtime/NetworkVariable/NetworkVariable.cs
+++ b/Runtime/NetworkVariable/NetworkVariable.cs
@@ -9,6 +9,55 @@ namespace Unity.Netcode
[Serializable]
public class NetworkVariable : NetworkVariableBase where T : unmanaged
{
+ // Functions that know how to serialize INetworkSerializable
+ internal static void WriteNetworkSerializable(FastBufferWriter writer, ref TForMethod value)
+ where TForMethod : INetworkSerializable, new()
+ {
+ writer.WriteNetworkSerializable(value);
+ }
+ internal static void ReadNetworkSerializable(FastBufferReader reader, out TForMethod value)
+ where TForMethod : INetworkSerializable, new()
+ {
+ reader.ReadNetworkSerializable(out value);
+ }
+
+ // Functions that serialize other types
+ private static void WriteValue(FastBufferWriter writer, ref TForMethod value) where TForMethod : unmanaged
+ {
+ writer.WriteValueSafe(value);
+ }
+
+ private static void ReadValue(FastBufferReader reader, out TForMethod value)
+ where TForMethod : unmanaged
+ {
+ reader.ReadValueSafe(out value);
+ }
+
+ internal delegate void WriteDelegate(FastBufferWriter writer, ref TForMethod value);
+
+ internal delegate void ReadDelegate(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 Write = WriteValue;
+ internal static ReadDelegate Read = ReadValue;
+
+
///
/// Delegate type for value changed event
///
@@ -106,7 +155,7 @@ public override void WriteDelta(FastBufferWriter writer)
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 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
///
public override void ReadField(FastBufferReader reader)
{
- reader.ReadValueSafe(out m_InternalValue);
+ Read(reader, out m_InternalValue);
}
///
public override void WriteField(FastBufferWriter writer)
{
- writer.WriteValueSafe(m_InternalValue);
+ Write(writer, ref m_InternalValue);
}
}
}
diff --git a/Runtime/NetworkVariable/NetworkVariableHelper.cs b/Runtime/NetworkVariable/NetworkVariableHelper.cs
new file mode 100644
index 0000000..1800910
--- /dev/null
+++ b/Runtime/NetworkVariable/NetworkVariableHelper.cs
@@ -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() where T : unmanaged, INetworkSerializable
+ {
+ NetworkVariable.Write = NetworkVariable.WriteNetworkSerializable;
+ NetworkVariable.Read = NetworkVariable.ReadNetworkSerializable;
+ }
+ }
+}
diff --git a/Runtime/Reflection/TypeExtensions.cs.meta b/Runtime/NetworkVariable/NetworkVariableHelper.cs.meta
similarity index 83%
rename from Runtime/Reflection/TypeExtensions.cs.meta
rename to Runtime/NetworkVariable/NetworkVariableHelper.cs.meta
index 80c06af..bbced6f 100644
--- a/Runtime/Reflection/TypeExtensions.cs.meta
+++ b/Runtime/NetworkVariable/NetworkVariableHelper.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 3e168a2bc1a1e2642af0369780fb560c
+guid: e54b65208bd3bbe4eaf62ca0384ae21f
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Runtime/Reflection.meta b/Runtime/Reflection.meta
deleted file mode 100644
index da604a3..0000000
--- a/Runtime/Reflection.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: af81f9951b096ff4cb8e4f8a4106104a
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Runtime/Reflection/TypeExtensions.cs b/Runtime/Reflection/TypeExtensions.cs
deleted file mode 100644
index 216e489..0000000
--- a/Runtime/Reflection/TypeExtensions.cs
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/Runtime/SceneManagement/NetworkSceneManager.cs b/Runtime/SceneManagement/NetworkSceneManager.cs
index 889b1f8..ec1ef0a 100644
--- a/Runtime/SceneManagement/NetworkSceneManager.cs
+++ b/Runtime/SceneManagement/NetworkSceneManager.cs
@@ -1028,8 +1028,8 @@ private void OnSceneUnloaded(uint sceneEventId)
// 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 +1344,8 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene)
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 +1830,18 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader)
/// Moves all NetworkObjects that don't have the set to
/// the "Do not destroy on load" scene.
///
- 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(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,24 +1904,22 @@ private void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePl
/// Moves all spawned NetworkObjects (from do not destroy on load) to the scene specified
///
/// scene to move the NetworkObjects to
- 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))
- {
- continue;
- }
-
- // Only move objects with no parent as child objects will follow
- if (sobj.gameObject.transform.parent == null)
+ // If it is in the DDOL then
+ if (sobj.gameObject.scene == DontDestroyOnLoadScene)
{
- // set it back to active at this point
- sobj.gameObject.SetActive(true);
- SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
+ // only move dynamically spawned network objects, with no parent as child objects will follow,
+ // back into the currently active scene
+ if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
+ {
+ SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
+ }
}
}
}
diff --git a/Runtime/Serialization/FastBufferWriter.cs b/Runtime/Serialization/FastBufferWriter.cs
index 0d906b0..5875e92 100644
--- a/Runtime/Serialization/FastBufferWriter.cs
+++ b/Runtime/Serialization/FastBufferWriter.cs
@@ -189,7 +189,7 @@ internal unsafe void Grow(int additionalSizeRequired)
var newSize = Math.Min(desiredSize, Handle->MaxCapacity);
byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf(), 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 @@ public void WriteNetworkSerializable(in T value) where T : INetworkSerializab
///
///
///
- public void WriteNetworkSerializable(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable
+ public void WriteNetworkSerializable(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable
{
int sizeInTs = count != -1 ? count : array.Length - offset;
WriteValueSafe(sizeInTs);
diff --git a/Runtime/Spawning/NetworkSpawnManager.cs b/Runtime/Spawning/NetworkSpawnManager.cs
index ad10715..e111656 100644
--- a/Runtime/Spawning/NetworkSpawnManager.cs
+++ b/Runtime/Spawning/NetworkSpawnManager.cs
@@ -27,6 +27,7 @@ private struct TriggerData
public MessageHeader Header;
public ulong SenderId;
public float Timestamp;
+ public int SerializedHeaderSize;
}
private struct TriggerInfo
{
@@ -117,7 +118,8 @@ internal unsafe void TriggerOnSpawn(ulong networkObjectId, FastBufferReader read
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 @@ internal unsafe void CleanupStaleTriggers()
m_Triggers.Remove(staleKeys[i]);
}
}
+ ///
+ /// 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.
+ ///
+ internal void CleanupAllTriggers()
+ {
+ foreach (var kvp in m_Triggers)
+ {
+ foreach (var data in kvp.Value.TriggerData)
+ {
+ data.Reader.Dispose();
+ }
+
+ kvp.Value.TriggerData.Dispose();
+ }
+
+ m_Triggers.Clear();
+ }
internal void RemoveOwnership(NetworkObject networkObject)
{
@@ -167,28 +187,44 @@ internal void RemoveOwnership(NetworkObject networkObject)
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)
+ {
+ return;
+ }
+
+ // Make sure the connected client entry exists before trying to remove ownership.
+ if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
- if (NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects[i] == networkObject)
+ for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
{
- NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.RemoveAt(i);
+ if (networkClient.OwnedObjects[i] == networkObject)
+ {
+ networkClient.OwnedObjects.RemoveAt(i);
+ }
}
- }
- networkObject.OwnerClientIdInternal = null;
+ networkObject.OwnerClientIdInternal = null;
- var message = new ChangeOwnershipMessage
- {
- NetworkObjectId = networkObject.NetworkObjectId,
- OwnerClientId = networkObject.OwnerClientId
- };
- var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
+ var message = new ChangeOwnershipMessage
+ {
+ NetworkObjectId = networkObject.NetworkObjectId,
+ OwnerClientId = networkObject.OwnerClientId
+ };
+ var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
- foreach (var client in NetworkManager.ConnectedClients)
+ foreach (var client in NetworkManager.ConnectedClients)
+ {
+ NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
+ }
+ }
+ else
{
- NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
+ }
}
}
@@ -207,7 +243,7 @@ private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient
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 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong
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,7 +616,7 @@ internal void ServerDestroySpawnedSceneObjects()
}
}
- internal void DestroyNonSceneObjects()
+ internal void DespawnAndDestroyNetworkObjects()
{
var networkObjects = UnityEngine.Object.FindObjectsOfType();
@@ -588,17 +624,25 @@ internal void DestroyNonSceneObjects()
{
if (networkObjects[i].NetworkManager == NetworkManager)
{
- if (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value == false)
+ if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
{
- if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
- {
- NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
- OnDespawnObject(networkObjects[i], false);
- }
- else
- {
- UnityEngine.Object.Destroy(networkObjects[i].gameObject);
- }
+ OnDespawnObject(networkObjects[i], false);
+ // Leave destruction up to the handler
+ NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
+ }
+ else if (networkObjects[i].IsSpawned)
+ {
+ // If it is an in-scene placed NetworkObject then just despawn
+ // and let it be destroyed when the scene is unloaded. Otherwise,
+ // despawn and destroy it.
+ var shouldDestroy = !(networkObjects[i].IsSceneObject != null
+ && networkObjects[i].IsSceneObject.Value);
+
+ OnDespawnObject(networkObjects[i], shouldDestroy);
+ }
+ else
+ {
+ UnityEngine.Object.Destroy(networkObjects[i].gameObject);
}
}
}
@@ -668,17 +712,21 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
return;
}
- // Move child NetworkObjects to the root when parent NetworkObject is destroyed
- foreach (var spawnedNetObj in SpawnedObjectsList)
+ // If we are shutting down the NetworkManager, then ignore resetting the parent
+ if (!NetworkManager.ShutdownInProgress)
{
- var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
- if (isReparented && latestParent == networkObject.NetworkObjectId)
+ // Move child NetworkObjects to the root when parent NetworkObject is destroyed
+ foreach (var spawnedNetObj in SpawnedObjectsList)
{
- spawnedNetObj.gameObject.transform.parent = null;
-
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
+ if (isReparented && latestParent == networkObject.NetworkObjectId)
{
- NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed");
+ spawnedNetObj.gameObject.transform.parent = null;
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed");
+ }
}
}
}
diff --git a/Tests/Editor/Messaging/MessageReceivingTests.cs b/Tests/Editor/Messaging/MessageReceivingTests.cs
index ef8bfda..90dc911 100644
--- a/Tests/Editor/Messaging/MessageReceivingTests.cs
+++ b/Tests/Editor/Messaging/MessageReceivingTests.cs
@@ -92,7 +92,7 @@ public void WhenHandlingAMessage_ReceiveMethodIsCalled()
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 @@ public void WhenReceivingAMessageAndProcessingMessageQueue_ReceiveMethodIsCalled
};
var messageHeader = new MessageHeader
{
- MessageSize = (ushort)UnsafeUtility.SizeOf(),
+ MessageSize = (uint)UnsafeUtility.SizeOf(),
MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)),
};
var message = GetMessage();
@@ -151,12 +151,10 @@ public void WhenReceivingAMessageAndProcessingMessageQueue_ReceiveMethodIsCalled
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 @@ public void WhenReceivingMultipleMessagesAndProcessingMessageQueue_ReceiveMethod
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)
diff --git a/Tests/Editor/Messaging/MessageSendingTests.cs b/Tests/Editor/Messaging/MessageSendingTests.cs
index d23c18a..ebf6b21 100644
--- a/Tests/Editor/Messaging/MessageSendingTests.cs
+++ b/Tests/Editor/Messaging/MessageSendingTests.cs
@@ -124,7 +124,7 @@ public void WhenSendingMultipleMessages_MessagesAreBatched()
public void WhenNotExceedingBatchSize_NewBatchesAreNotCreated()
{
var message = GetMessage();
- var size = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf();
+ var size = UnsafeUtility.SizeOf() + 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 @@ public void WhenNotExceedingBatchSize_NewBatchesAreNotCreated()
public void WhenExceedingBatchSize_NewBatchesAreCreated()
{
var message = GetMessage();
- var size = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf();
+ var size = UnsafeUtility.SizeOf() + 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 @@ public void WhenExceedingBatchSize_NewBatchesAreCreated()
public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated()
{
var message = GetMessage();
- var size = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf();
+ var size = UnsafeUtility.SizeOf() + 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 @@ public void WhenSendingMessaged_SentDataIsCorrect()
var reader = new FastBufferReader(m_MessageSender.MessageQueue[0], Allocator.Temp);
using (reader)
{
- reader.TryBeginRead(
- FastBufferWriter.GetWriteSize() +
- FastBufferWriter.GetWriteSize() * 2 +
- FastBufferWriter.GetWriteSize() * 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(), 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(), 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(), messageHeader.MessageSize);
+ reader.ReadValueSafe(out TestMessage receivedMessage2);
Assert.AreEqual(message2, receivedMessage2);
}
}
diff --git a/Tests/Editor/SnapshotRttTests.cs b/Tests/Editor/SnapshotRttTests.cs
index bb3aa7b..697af30 100644
--- a/Tests/Editor/SnapshotRttTests.cs
+++ b/Tests/Editor/SnapshotRttTests.cs
@@ -9,7 +9,7 @@ public class SnapshotRttTests
[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 @@ public void TestBasicRtt()
[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;
diff --git a/Tests/Editor/SnapshotTests.cs b/Tests/Editor/SnapshotTests.cs
new file mode 100644
index 0000000..2ab3354
--- /dev/null
+++ b/Tests/Editor/SnapshotTests.cs
@@ -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(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(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 { 1 };
+ m_SendSnapshot.Spawn(command);
+ }
+
+ private void SendDespawnToSnapshot(ulong objectId)
+ {
+ SnapshotDespawnCommand command = default;
+ // identity
+ command.NetworkObjectId = objectId;
+ command.TargetClientIds = new List { 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);
+ }
+ }
+}
+
diff --git a/Runtime/Metrics/StreamExtensions.cs.meta b/Tests/Editor/SnapshotTests.cs.meta
similarity index 83%
rename from Runtime/Metrics/StreamExtensions.cs.meta
rename to Tests/Editor/SnapshotTests.cs.meta
index 3eb9911..294c508 100644
--- a/Runtime/Metrics/StreamExtensions.cs.meta
+++ b/Tests/Editor/SnapshotTests.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 61dd9b1558f6d7c46ad323b2c2c03c29
+guid: 3d41788be1de34b7c8bcfce6a2877754
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Tests/Editor/StartStopTests.cs b/Tests/Editor/StartStopTests.cs
index 9db67ae..f232795 100644
--- a/Tests/Editor/StartStopTests.cs
+++ b/Tests/Editor/StartStopTests.cs
@@ -45,13 +45,26 @@ public void TestStartupServerState()
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()
{
diff --git a/Tests/Editor/com.unity.netcode.editortests.asmdef b/Tests/Editor/com.unity.netcode.editortests.asmdef
index d70259e..25626d1 100644
--- a/Tests/Editor/com.unity.netcode.editortests.asmdef
+++ b/Tests/Editor/com.unity.netcode.editortests.asmdef
@@ -2,6 +2,7 @@
"name": "Unity.Netcode.EditorTests",
"rootNamespace": "Unity.Netcode.EditorTests",
"references": [
+ "Unity.Collections",
"Unity.Netcode.Runtime",
"Unity.Netcode.Editor",
"Unity.Netcode.Components",
diff --git a/Tests/Runtime/Metrics/MessagingMetricsTests.cs b/Tests/Runtime/Metrics/MessagingMetricsTests.cs
index 675e08c..ad3cec2 100644
--- a/Tests/Runtime/Metrics/MessagingMetricsTests.cs
+++ b/Tests/Runtime/Metrics/MessagingMetricsTests.cs
@@ -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();
- private static readonly int k_UnnamedMessageOverhead = FastBufferWriter.GetWriteSize();
+ // 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;
diff --git a/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs
index 31151d2..853bd73 100644
--- a/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs
+++ b/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs
@@ -14,6 +14,8 @@ internal class OwnershipChangeMetricsTests : SingleClientMetricTestBase
{
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 UpdatePlayerPrefab => _ =>
{
@@ -58,7 +60,7 @@ public IEnumerator TrackOwnershipChangeSentMetric()
var ownershipChangeSent = metricValues.First();
Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId);
Assert.AreEqual(Server.LocalClientId, ownershipChangeSent.Connection.Id);
- Assert.AreEqual(FastBufferWriter.GetWriteSize() + FastBufferWriter.GetWriteSize(), ownershipChangeSent.BytesCount);
+ Assert.AreEqual(FastBufferWriter.GetWriteSize() + k_MessageHeaderSize, ownershipChangeSent.BytesCount);
}
[UnityTest]
diff --git a/Tests/Runtime/Metrics/ServerLogsMetricTests.cs b/Tests/Runtime/Metrics/ServerLogsMetricTests.cs
index 6471cbf..8036dfd 100644
--- a/Tests/Runtime/Metrics/ServerLogsMetricTests.cs
+++ b/Tests/Runtime/Metrics/ServerLogsMetricTests.cs
@@ -11,7 +11,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class ServerLogsMetricTests : SingleClientMetricTestBase
{
- private static readonly int k_ServerLogSentMessageOverhead = 2 + FastBufferWriter.GetWriteSize();
+ // 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]
diff --git a/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs b/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs
index 6ba1029..2e8986c 100644
--- a/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs
+++ b/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs
@@ -13,7 +13,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class TransportBytesMetricsTests : SingleClientMetricTestBase
{
- static readonly long MessageOverhead = 8 + FastBufferWriter.GetWriteSize() + FastBufferWriter.GetWriteSize();
+ // 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() + k_MessageHeaderSize;
[UnityTest]
public IEnumerator TrackTotalNumberOfBytesSent()
diff --git a/Tests/Runtime/MultiInstanceHelpers.cs b/Tests/Runtime/MultiInstanceHelpers.cs
index bfcc44d..70abe74 100644
--- a/Tests/Runtime/MultiInstanceHelpers.cs
+++ b/Tests/Runtime/MultiInstanceHelpers.cs
@@ -450,7 +450,7 @@ public static IEnumerator RunAndWaitForCondition(Action workload, Func pre
if (!waitResult.Result)
{
- throw new Exception();
+ Assert.Fail("Predicate condition failed");
}
}
diff --git a/Tests/Runtime/NetworkBehaviourUpdaterTests.cs b/Tests/Runtime/NetworkBehaviourUpdaterTests.cs
index 0863787..7ebec09 100644
--- a/Tests/Runtime/NetworkBehaviourUpdaterTests.cs
+++ b/Tests/Runtime/NetworkBehaviourUpdaterTests.cs
@@ -103,6 +103,8 @@ void AddNetworkBehaviour(Type type, GameObject prefab)
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)
{
diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs
new file mode 100644
index 0000000..8a15e2e
--- /dev/null
+++ b/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs
@@ -0,0 +1,132 @@
+using System.Collections;
+using NUnit.Framework;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+
+namespace Unity.Netcode.RuntimeTests
+{
+ ///
+ /// Tests that check OnNetworkDespawn being invoked
+ ///
+ 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();
+ m_ObjectToSpawn.AddComponent();
+
+ // 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
+ }
+
+ ///
+ /// Tests that a spawned NetworkObject's associated NetworkBehaviours will have
+ /// their OnNetworkDespawn invoked during NetworkManager shutdown.
+ ///
+ [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();
+ spawnedNetworkObject.NetworkManagerOwner = m_ServerHost;
+ spawnedNetworkObject.Spawn(true);
+
+ // Get the spawned object relative to which NetworkManager instance we are testing.
+ var relativeSpawnedObject = new MultiInstanceHelpers.CoroutineResultWrapper();
+ yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.GetComponent() != null), networkManager, relativeSpawnedObject));
+ var onNetworkDespawnTestComponent = relativeSpawnedObject.Result.GetComponent();
+
+ // 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);
+ }
+ }
+}
+
diff --git a/Runtime/Collections/FixedQueue.cs.meta b/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs.meta
similarity index 83%
rename from Runtime/Collections/FixedQueue.cs.meta
rename to Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs.meta
index bc855dd..895a567 100644
--- a/Runtime/Collections/FixedQueue.cs.meta
+++ b/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: a8514b4eca0c7044d9b92faf9407ec93
+guid: 93f8ca7aa8b616746a1c15592830b047
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs
index 26e2486..c5bdc8b 100644
--- a/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs
+++ b/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs
@@ -115,6 +115,9 @@ public IEnumerator TestOwnershipCallbacks()
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];
diff --git a/Tests/Runtime/NetworkVariableTests.cs b/Tests/Runtime/NetworkVariableTests.cs
index 600883b..4325a7e 100644
--- a/Tests/Runtime/NetworkVariableTests.cs
+++ b/Tests/Runtime/NetworkVariableTests.cs
@@ -11,9 +11,19 @@ public struct TestStruct : INetworkSerializable
{
public uint SomeInt;
public bool SomeBool;
+ public static bool NetworkSerializeCalledOnWrite;
+ public static bool NetworkSerializeCalledOnRead;
public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter
{
+ if (serializer.IsReader)
+ {
+ NetworkSerializeCalledOnRead = true;
+ }
+ else
+ {
+ NetworkSerializeCalledOnWrite = true;
+ }
serializer.SerializeValue(ref SomeInt);
serializer.SerializeValue(ref SomeBool);
}
@@ -409,6 +419,28 @@ public IEnumerator TestNetworkVariableStruct([Values(true, false)] bool useHost)
);
}
+ [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()
{
diff --git a/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs b/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs
index 1a9f2a8..d053704 100644
--- a/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs
+++ b/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs
@@ -70,6 +70,7 @@ public IEnumerator TestRigidbodyKinematicEnableDisable()
yield return NetworkRigidbodyTestBase.WaitForFrames(5);
+ // This should equal Kinematic
Assert.IsTrue(serverPlayer.GetComponent().isKinematic == Kinematic);
yield return NetworkRigidbodyTestBase.WaitForFrames(5);
diff --git a/Tests/Runtime/RpcPipelineTestComponent.cs b/Tests/Runtime/RpcPipelineTestComponent.cs
deleted file mode 100644
index 07e7fee..0000000
--- a/Tests/Runtime/RpcPipelineTestComponent.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-using System;
-using System.Collections.Generic;
-using UnityEngine;
-
-namespace Unity.Netcode.RuntimeTests
-{
- ///
- /// 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.
- ///
- public class RpcPipelineTestComponent : NetworkBehaviour
- {
- ///
- /// Allows the external RPCQueueTest to begin testing or stop it
- ///
- public bool PingSelfEnabled;
-
- ///
- /// How many times will we iterate through the various NetworkUpdateStage values?
- /// (defaults to 2)
- ///
- 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;
- }
-
- ///
- /// Determine if we have iterated over more than our maximum stages we want to test
- ///
- /// true or false (did we exceed the max iterations or not?)
- public bool ExceededMaxIterations()
- {
- if (m_StagesSent.Count > m_MaxStagesSent && m_MaxStagesSent > 0)
- {
- return true;
- }
-
- return false;
- }
-
- ///
- /// Returns back whether the test has completed the total number of iterations
- ///
- ///
- 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 m_ServerStagesReceived = new List();
- private readonly List m_ClientStagesReceived = new List();
- private readonly List m_StagesSent = new List();
-
- ///
- /// Assures all update stages were in alginment with one another
- ///
- /// true or false
- 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;
- }
-
- ///
- /// Server side RPC for testing
- ///
- /// server rpc parameters
- [ServerRpc]
- private void PingMySelfServerRpc(int currentCount, ServerRpcParams parameters = default)
- {
- Debug.Log($"{nameof(PingMySelfServerRpc)}: [HostClient][ServerRpc][{currentCount}] invoked.");
-
- PingMySelfClientRpc(currentCount, m_ClientParams);
- }
-
- ///
- /// Client Side RPC called by PingMySelfServerRPC to validate both Client->Server and Server-Client pipeline is working
- ///
- /// client rpc parameters
- [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;
- }
- }
-}
diff --git a/Tests/Runtime/RpcTests.cs b/Tests/Runtime/RpcTests.cs
index 50ca4f6..2d6e96b 100644
--- a/Tests/Runtime/RpcTests.cs
+++ b/Tests/Runtime/RpcTests.cs
@@ -10,13 +10,13 @@ public class RpcTests : BaseMultiInstanceTest
{
public class RpcTestNB : NetworkBehaviour
{
- public event Action OnServer_Rpc;
+ public event Action 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 @@ public IEnumerator TestRpcs()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper();
- 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();
- 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 @@ public IEnumerator TestRpcs()
hasReceivedClientRpcRemotely = true;
};
- clientClientPlayerResult.Result.GetComponent().OnServer_Rpc += () =>
+ clientClientPlayerResult.Result.GetComponent().OnServer_Rpc += (clientId, param) =>
{
// The RPC invoked locally. (Weaver failure?)
Assert.Fail("ServerRpc invoked locally. Weaver failure?");
};
- serverClientPlayerResult.Result.GetComponent().OnServer_Rpc += () =>
+ serverClientPlayerResult.Result.GetComponent().OnServer_Rpc += (clientId, param) =>
{
Debug.Log("ServerRpc received on server object");
+ Assert.True(param.Receive.SenderClientId == clientId);
hasReceivedServerRpc = true;
};
@@ -79,7 +81,7 @@ public IEnumerator TestRpcs()
};
// Send ServerRpc
- clientClientPlayerResult.Result.GetComponent().MyServerRpc();
+ clientClientPlayerResult.Result.GetComponent().MyServerRpc(clientId);
// Send ClientRpc
serverClientPlayerResult.Result.GetComponent().MyClientRpc();
diff --git a/Tests/Runtime/StopStartRuntimeTests.cs b/Tests/Runtime/StopStartRuntimeTests.cs
new file mode 100644
index 0000000..aa94fd9
--- /dev/null
+++ b/Tests/Runtime/StopStartRuntimeTests.cs
@@ -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.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();
+ }
+ }
+ }
+}
diff --git a/Tests/Runtime/RpcPipelineTestComponent.cs.meta b/Tests/Runtime/StopStartRuntimeTests.cs.meta
similarity index 83%
rename from Tests/Runtime/RpcPipelineTestComponent.cs.meta
rename to Tests/Runtime/StopStartRuntimeTests.cs.meta
index 2c945db..8df3e77 100644
--- a/Tests/Runtime/RpcPipelineTestComponent.cs.meta
+++ b/Tests/Runtime/StopStartRuntimeTests.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: e946a6fdcfcb9dd48b76b38871c0a77b
+guid: 97a5298e33ee4d32be46ce84fecdcd06
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Tests/Runtime/Timing/TimeMultiInstanceTest.cs b/Tests/Runtime/Timing/TimeMultiInstanceTest.cs
index c182b53..ccba585 100644
--- a/Tests/Runtime/Timing/TimeMultiInstanceTest.cs
+++ b/Tests/Runtime/Timing/TimeMultiInstanceTest.cs
@@ -56,8 +56,8 @@ public IEnumerator TestTimeMultiInstance(int targetFrameRate, uint tickRate)
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);
diff --git a/Tests/Runtime/Transport/SIPTransport.cs b/Tests/Runtime/Transport/SIPTransport.cs
index 9ea8816..6dfa181 100644
--- a/Tests/Runtime/Transport/SIPTransport.cs
+++ b/Tests/Runtime/Transport/SIPTransport.cs
@@ -112,6 +112,7 @@ private void StopServer()
{
s_Server = null;
m_Peers.Remove(ServerClientId);
+ m_LocalConnection = null;
}
public override void Shutdown()
diff --git a/package.json b/package.json
index a29572d..b858cf0 100644
--- a/package.json
+++ b/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.4",
"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": "28fd203207af71c78d6aaba679e94c5ead807529"
},
"repository": {
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
"type": "git",
- "revision": "3e4df72dadeea8bd622da2824e30541910c79d3d"
+ "revision": "b14204cd059fb90f4a64319824105619f317d591"
},
"samples": [
{