From 4d70c198bdfa677a9481ad5245fe89e910123fa3 Mon Sep 17 00:00:00 2001
From: Unity Technologies <@unity>
Date: Wed, 7 Jun 2023 00:00:00 +0000
Subject: [PATCH] com.unity.netcode.gameobjects@1.5.1 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.5.1] - 2023-06-07
### Added
- Added support for serializing `NativeArray<>` and `NativeList<>` in `FastBufferReader`/`FastBufferWriter`, `BufferSerializer`, `NetworkVariable`, and RPCs. (To use `NativeList<>`, add `UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT` to your Scripting Define Symbols in `Project Settings > Player`) (#2375)
- The location of the automatically-created default network prefab list can now be configured (#2544)
- Added: Message size limits (max single message and max fragmented message) can now be set using NetworkManager.MaximumTransmissionUnitSize and NetworkManager.MaximumFragmentedMessageSize for transports that don't work with the default values (#2530)
- Added `NetworkObject.SpawnWithObservers` property (default is true) that when set to false will spawn a `NetworkObject` with no observers and will not be spawned on any client until `NetworkObject.NetworkShow` is invoked. (#2568)
### Fixed
- Fixed: Fixed a null reference in codegen in some projects (#2581)
- Fixed issue where the `OnClientDisconnected` client identifier was incorrect after a pending client connection was denied. (#2569)
- Fixed warning "Runtime Network Prefabs was not empty at initialization time." being erroneously logged when no runtime network prefabs had been added (#2565)
- Fixed issue where some temporary debug console logging was left in a merged PR. (#2562)
- Fixed the "Generate Default Network Prefabs List" setting not loading correctly and always reverting to being checked. (#2545)
- Fixed issue where users could not use NetworkSceneManager.VerifySceneBeforeLoading to exclude runtime generated scenes from client synchronization. (#2550)
- Fixed missing value on `NetworkListEvent` for `EventType.RemoveAt` events. (#2542,#2543)
- Fixed issue where parenting a NetworkTransform under a transform with a scale other than Vector3.one would result in incorrect values on non-authoritative instances. (#2538)
- Fixed issue where a server would include scene migrated and then despawned NetworkObjects to a client that was being synchronized. (#2532)
- Fixed the inspector throwing exceptions when attempting to render `NetworkVariable`s of enum types. (#2529)
- Making a `NetworkVariable` with an `INetworkSerializable` type that doesn't meet the `new()` constraint will now create a compile-time error instead of an editor crash (#2528)
- Fixed Multiplayer Tools package installation docs page link on the NetworkManager popup. (#2526)
- Fixed an exception and error logging when two different objects are shown and hidden on the same frame (#2524)
- Fixed a memory leak in `UnityTransport` that occurred if `StartClient` failed. (#2518)
- Fixed issue where a client could throw an exception if abruptly disconnected from a network session with one or more spawned `NetworkObject`(s). (#2510)
- Fixed issue where invalid endpoint addresses were not being detected and returning false from NGO UnityTransport. (#2496)
- Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495)
## Changed
- Adding network prefabs before NetworkManager initialization is now supported. (#2565)
- Connecting clients being synchronized now switch to the server's active scene before spawning and synchronizing NetworkObjects. (#2532)
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.4. (#2533)
- Improved performance of NetworkBehaviour initialization by replacing reflection when initializing NetworkVariables with compile-time code generation, which should help reduce hitching during additive scene loads. (#2522)
---
CHANGELOG.md | 36 +
Components/NetworkTransform.cs | 51 +-
Documentation~/index.md | 6 +-
Editor/CodeGen/CodeGenHelpers.cs | 55 +
Editor/CodeGen/INetworkMessageILPP.cs | 89 +-
Editor/CodeGen/NetworkBehaviourILPP.cs | 512 +++-
Editor/CodeGen/RuntimeAccessModifiersILPP.cs | 7 +-
.../NetcodeForGameObjectsProjectSettings.cs | 30 +
...tcodeForGameObjectsProjectSettings.cs.meta | 3 +
.../NetcodeForGameObjectsSettings.cs | 13 -
.../Configuration/NetcodeSettingsProvider.cs | 64 +
.../Configuration/NetworkPrefabProcessor.cs | 5 +-
Editor/NetworkBehaviourEditor.cs | 42 +-
Editor/NetworkManagerEditor.cs | 2 +-
Runtime/Configuration/NetworkConfig.cs | 4 +-
Runtime/Configuration/NetworkPrefab.cs | 47 +-
Runtime/Configuration/NetworkPrefabs.cs | 40 +-
Runtime/Connection/NetworkClient.cs | 64 +-
.../Connection/NetworkConnectionManager.cs | 1109 +++++++
.../NetworkConnectionManager.cs.meta | 11 +
Runtime/Connection/PendingClient.cs | 5 +
Runtime/Core/ComponentFactory.cs | 2 +-
Runtime/Core/NetworkBehaviour.cs | 83 +-
Runtime/Core/NetworkBehaviourUpdater.cs | 31 +-
Runtime/Core/NetworkManager.cs | 2660 +++++------------
Runtime/Core/NetworkObject.cs | 41 +-
Runtime/Logging/NetworkLog.cs | 41 +-
Runtime/Messaging/CustomMessageManager.cs | 8 +-
Runtime/Messaging/DefaultMessageSender.cs | 26 +
.../Messaging/DefaultMessageSender.cs.meta | 11 +
Runtime/Messaging/DeferredMessageManager.cs | 14 +-
Runtime/Messaging/DisconnectReasonMessage.cs | 17 +-
...r.cs => IDeferredNetworkMessageManager.cs} | 2 +-
...=> IDeferredNetworkMessageManager.cs.meta} | 0
Runtime/Messaging/ILPPMessageProvider.cs | 6 +-
Runtime/Messaging/IMessageProvider.cs | 9 -
Runtime/Messaging/INetworkMessage.cs | 1 -
Runtime/Messaging/INetworkMessageProvider.cs | 9 +
...s.meta => INetworkMessageProvider.cs.meta} | 0
...sageSender.cs => INetworkMessageSender.cs} | 2 +-
....cs.meta => INetworkMessageSender.cs.meta} | 0
.../Messages/ChangeOwnershipMessage.cs | 2 +-
.../Messages/ConnectionApprovedMessage.cs | 15 +-
.../Messages/ConnectionRequestMessage.cs | 21 +-
.../Messaging/Messages/CreateObjectMessage.cs | 2 +-
.../Messages/DestroyObjectMessage.cs | 2 +-
.../Messages/NetworkVariableDeltaMessage.cs | 8 +-
.../Messaging/Messages/ParentSyncMessage.cs | 2 +-
Runtime/Messaging/Messages/RpcMessages.cs | 2 +-
.../{BatchHeader.cs => NetworkBatchHeader.cs} | 2 +-
...der.cs.meta => NetworkBatchHeader.cs.meta} | 0
Runtime/Messaging/NetworkContext.cs | 4 +-
Runtime/Messaging/NetworkManagerHooks.cs | 119 +
Runtime/Messaging/NetworkManagerHooks.cs.meta | 11 +
...ssageHeader.cs => NetworkMessageHeader.cs} | 8 +-
...r.cs.meta => NetworkMessageHeader.cs.meta} | 0
...gingSystem.cs => NetworkMessageManager.cs} | 214 +-
....cs.meta => NetworkMessageManager.cs.meta} | 0
Runtime/Metrics/NetworkMetricsManager.cs | 44 +
Runtime/Metrics/NetworkMetricsManager.cs.meta | 11 +
.../Collections/NetworkList.cs | 6 +-
Runtime/NetworkVariable/NetworkVariable.cs | 86 +
.../NetworkVariable/NetworkVariableBase.cs | 3 +-
.../NetworkVariableSerialization.cs | 584 +++-
Runtime/Profiling/ProfilingHooks.cs | 8 +-
.../DefaultSceneManagerHandler.cs | 2 +-
.../SceneManagement/NetworkSceneManager.cs | 261 +-
Runtime/Serialization/BufferSerializer.cs | 68 +-
.../Serialization/BufferSerializerReader.cs | 40 +
.../Serialization/BufferSerializerWriter.cs | 37 +
Runtime/Serialization/FastBufferReader.cs | 322 +-
Runtime/Serialization/FastBufferWriter.cs | 443 ++-
Runtime/Serialization/IReaderWriter.cs | 67 +-
Runtime/Spawning/NetworkPrefabHandler.cs | 177 +-
Runtime/Spawning/NetworkSpawnManager.cs | 79 +-
Runtime/Timing/NetworkTickSystem.cs | 2 +-
Runtime/Timing/NetworkTimeSystem.cs | 124 +-
Runtime/Transports/UTP/UnityTransport.cs | 50 +-
.../Runtime/MessageHooksConditional.cs | 6 +-
TestHelpers/Runtime/NetcodeIntegrationTest.cs | 52 +-
.../Runtime/NetcodeIntegrationTestHelpers.cs | 6 +-
TestHelpers/Runtime/NetcodeLogAssert.cs | 38 +-
.../Editor/Messaging/DisconnectOnSendTests.cs | 92 +
.../Messaging/DisconnectOnSendTests.cs.meta | 3 +
.../Messaging/MessageCorruptionTests.cs | 38 +-
.../Editor/Messaging/MessageReceivingTests.cs | 72 +-
.../Messaging/MessageRegistrationTests.cs | 90 +-
Tests/Editor/Messaging/MessageSendingTests.cs | 133 +-
.../Messaging/MessageVersioningTests.cs | 166 +-
Tests/Editor/Messaging/NopMessageSender.cs | 2 +-
.../NetworkManagerConfigurationTests.cs | 161 +-
.../BaseFastBufferReaderWriterTest.cs | 754 ++++-
.../Serialization/FastBufferReaderTests.cs | 365 +++
.../Serialization/FastBufferWriterTests.cs | 242 ++
.../Editor/Transports/UnityTransportTests.cs | 20 +-
Tests/Runtime/ClientApprovalDenied.cs | 72 +
Tests/Runtime/ClientApprovalDenied.cs.meta | 11 +
.../NetworkVariableTestComponent.cs | 25 +-
.../Runtime/ConnectionApprovalTimeoutTests.cs | 14 +-
Tests/Runtime/DeferredMessagingTests.cs | 110 +-
Tests/Runtime/DisconnectTests.cs | 212 +-
Tests/Runtime/InvalidConnectionEventsTest.cs | 16 +-
.../Runtime/Metrics/ServerLogsMetricTests.cs | 8 +
.../Metrics/TransportBytesMetricsTests.cs | 2 +-
Tests/Runtime/NetworkBehaviourUpdaterTests.cs | 2 +-
Tests/Runtime/NetworkManagerEventsTests.cs | 24 -
Tests/Runtime/NetworkManagerTransportTests.cs | 8 +-
.../NetworkObjectDestroyTests.cs | 69 +-
.../NetworkObjectOnSpawnTests.cs | 85 +
Tests/Runtime/NetworkShowHideTests.cs | 30 +
.../NetworkTransform/NetworkTransformTests.cs | 212 +-
Tests/Runtime/NetworkVariableTests.cs | 1275 +++++++-
...tworkVariableUserSerializableTypesTests.cs | 8 +
Tests/Runtime/RpcTests.cs | 63 +-
Tests/Runtime/RpcTypeSerializationTests.cs | 1987 ++++++++++++
.../Runtime/RpcTypeSerializationTests.cs.meta | 3 +
Tests/Runtime/TestHelpers/MessageCatcher.cs | 4 +-
.../UnityTransportConnectionTests.cs | 16 +
package.json | 12 +-
119 files changed, 11328 insertions(+), 3164 deletions(-)
create mode 100644 Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs
create mode 100644 Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs.meta
create mode 100644 Runtime/Connection/NetworkConnectionManager.cs
create mode 100644 Runtime/Connection/NetworkConnectionManager.cs.meta
create mode 100644 Runtime/Messaging/DefaultMessageSender.cs
create mode 100644 Runtime/Messaging/DefaultMessageSender.cs.meta
rename Runtime/Messaging/{IDeferredMessageManager.cs => IDeferredNetworkMessageManager.cs} (96%)
rename Runtime/Messaging/{IDeferredMessageManager.cs.meta => IDeferredNetworkMessageManager.cs.meta} (100%)
delete mode 100644 Runtime/Messaging/IMessageProvider.cs
create mode 100644 Runtime/Messaging/INetworkMessageProvider.cs
rename Runtime/Messaging/{IMessageProvider.cs.meta => INetworkMessageProvider.cs.meta} (100%)
rename Runtime/Messaging/{IMessageSender.cs => INetworkMessageSender.cs} (74%)
rename Runtime/Messaging/{IMessageSender.cs.meta => INetworkMessageSender.cs.meta} (100%)
rename Runtime/Messaging/{BatchHeader.cs => NetworkBatchHeader.cs} (91%)
rename Runtime/Messaging/{BatchHeader.cs.meta => NetworkBatchHeader.cs.meta} (100%)
create mode 100644 Runtime/Messaging/NetworkManagerHooks.cs
create mode 100644 Runtime/Messaging/NetworkManagerHooks.cs.meta
rename Runtime/Messaging/{MessageHeader.cs => NetworkMessageHeader.cs} (61%)
rename Runtime/Messaging/{MessageHeader.cs.meta => NetworkMessageHeader.cs.meta} (100%)
rename Runtime/Messaging/{MessagingSystem.cs => NetworkMessageManager.cs} (83%)
rename Runtime/Messaging/{MessagingSystem.cs.meta => NetworkMessageManager.cs.meta} (100%)
create mode 100644 Runtime/Metrics/NetworkMetricsManager.cs
create mode 100644 Runtime/Metrics/NetworkMetricsManager.cs.meta
create mode 100644 Tests/Editor/Messaging/DisconnectOnSendTests.cs
create mode 100644 Tests/Editor/Messaging/DisconnectOnSendTests.cs.meta
create mode 100644 Tests/Runtime/ClientApprovalDenied.cs
create mode 100644 Tests/Runtime/ClientApprovalDenied.cs.meta
create mode 100644 Tests/Runtime/RpcTypeSerializationTests.cs
create mode 100644 Tests/Runtime/RpcTypeSerializationTests.cs.meta
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 098ab31..46cc459 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,42 @@ 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.5.1] - 2023-06-07
+
+### Added
+
+- Added support for serializing `NativeArray<>` and `NativeList<>` in `FastBufferReader`/`FastBufferWriter`, `BufferSerializer`, `NetworkVariable`, and RPCs. (To use `NativeList<>`, add `UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT` to your Scripting Define Symbols in `Project Settings > Player`) (#2375)
+- The location of the automatically-created default network prefab list can now be configured (#2544)
+- Added: Message size limits (max single message and max fragmented message) can now be set using NetworkManager.MaximumTransmissionUnitSize and NetworkManager.MaximumFragmentedMessageSize for transports that don't work with the default values (#2530)
+- Added `NetworkObject.SpawnWithObservers` property (default is true) that when set to false will spawn a `NetworkObject` with no observers and will not be spawned on any client until `NetworkObject.NetworkShow` is invoked. (#2568)
+
+### Fixed
+
+- Fixed: Fixed a null reference in codegen in some projects (#2581)
+- Fixed issue where the `OnClientDisconnected` client identifier was incorrect after a pending client connection was denied. (#2569)
+- Fixed warning "Runtime Network Prefabs was not empty at initialization time." being erroneously logged when no runtime network prefabs had been added (#2565)
+- Fixed issue where some temporary debug console logging was left in a merged PR. (#2562)
+- Fixed the "Generate Default Network Prefabs List" setting not loading correctly and always reverting to being checked. (#2545)
+- Fixed issue where users could not use NetworkSceneManager.VerifySceneBeforeLoading to exclude runtime generated scenes from client synchronization. (#2550)
+- Fixed missing value on `NetworkListEvent` for `EventType.RemoveAt` events. (#2542,#2543)
+- Fixed issue where parenting a NetworkTransform under a transform with a scale other than Vector3.one would result in incorrect values on non-authoritative instances. (#2538)
+- Fixed issue where a server would include scene migrated and then despawned NetworkObjects to a client that was being synchronized. (#2532)
+- Fixed the inspector throwing exceptions when attempting to render `NetworkVariable`s of enum types. (#2529)
+- Making a `NetworkVariable` with an `INetworkSerializable` type that doesn't meet the `new()` constraint will now create a compile-time error instead of an editor crash (#2528)
+- Fixed Multiplayer Tools package installation docs page link on the NetworkManager popup. (#2526)
+- Fixed an exception and error logging when two different objects are shown and hidden on the same frame (#2524)
+- Fixed a memory leak in `UnityTransport` that occurred if `StartClient` failed. (#2518)
+- Fixed issue where a client could throw an exception if abruptly disconnected from a network session with one or more spawned `NetworkObject`(s). (#2510)
+- Fixed issue where invalid endpoint addresses were not being detected and returning false from NGO UnityTransport. (#2496)
+- Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495)
+
+## Changed
+
+- Adding network prefabs before NetworkManager initialization is now supported. (#2565)
+- Connecting clients being synchronized now switch to the server's active scene before spawning and synchronizing NetworkObjects. (#2532)
+- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.4. (#2533)
+- Improved performance of NetworkBehaviour initialization by replacing reflection when initializing NetworkVariables with compile-time code generation, which should help reduce hitching during additive scene loads. (#2522)
+
## [1.4.0] - 2023-04-10
### Added
diff --git a/Components/NetworkTransform.cs b/Components/NetworkTransform.cs
index 04a8837..587b263 100644
--- a/Components/NetworkTransform.cs
+++ b/Components/NetworkTransform.cs
@@ -2392,16 +2392,6 @@ namespace Unity.Netcode.Components
m_CachedNetworkManager = NetworkManager;
Initialize();
- // This assures the initial spawning of the object synchronizes all connected clients
- // with the current transform values. This should not be placed within Initialize since
- // that can be invoked when ownership changes.
- if (CanCommitToTransform)
- {
- var currentPosition = GetSpaceRelativePosition();
- var currentRotation = GetSpaceRelativeRotation();
- // Teleport to current position
- SetStateInternal(currentPosition, currentRotation, transform.localScale, true);
- }
}
///
@@ -2472,6 +2462,7 @@ namespace Unity.Netcode.Components
CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner;
var replicatedState = ReplicatedNetworkState;
var currentPosition = GetSpaceRelativePosition();
+ var currentRotation = GetSpaceRelativeRotation();
if (CanCommitToTransform)
{
@@ -2483,6 +2474,9 @@ namespace Unity.Netcode.Components
// Authority only updates once per network tick
NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
NetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick;
+
+ // Teleport to current position
+ SetStateInternal(currentPosition, currentRotation, transform.localScale, true);
}
else
{
@@ -2494,15 +2488,42 @@ namespace Unity.Netcode.Components
NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
ResetInterpolatedStateToCurrentAuthoritativeState();
- m_CurrentPosition = GetSpaceRelativePosition();
+ m_CurrentPosition = currentPosition;
m_CurrentScale = transform.localScale;
- m_CurrentRotation = GetSpaceRelativeRotation();
+ m_CurrentRotation = currentRotation;
}
OnInitialize(ref replicatedState);
}
+ ///
+ ///
+ /// When a parent changes, non-authoritative instances should:
+ /// - Apply the resultant position, rotation, and scale from the parenting action.
+ /// - Clear interpolators (even if not enabled on this frame)
+ /// - Reset the interpolators to the position, rotation, and scale resultant values.
+ /// This prevents interpolation visual anomalies and issues during initial synchronization
+ ///
+ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
+ {
+ // Only if we are not authority
+ if (!CanCommitToTransform)
+ {
+ m_CurrentPosition = GetSpaceRelativePosition();
+ m_CurrentRotation = GetSpaceRelativeRotation();
+ m_CurrentScale = GetScale();
+ m_ScaleInterpolator.Clear();
+ m_PositionInterpolator.Clear();
+ m_RotationInterpolator.Clear();
+ var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, NetworkManager.ServerTime.Tick).Time;
+ UpdatePositionInterpolator(m_CurrentPosition, tempTime, true);
+ m_ScaleInterpolator.ResetTo(m_CurrentScale, tempTime);
+ m_RotationInterpolator.ResetTo(m_CurrentRotation, tempTime);
+ }
+ base.OnNetworkObjectParentChanged(parentNetworkObject);
+ }
+
///
/// Directly sets a state on the authoritative transform.
/// Owner clients can directly set the state on a server authoritative transform
@@ -2656,6 +2677,12 @@ namespace Unity.Netcode.Components
}
}
+ // If we have not received any additional state updates since the very
+ // initial synchronization, then exit early.
+ if (m_LocalAuthoritativeNetworkState.IsSynchronizing)
+ {
+ return;
+ }
// Apply the current authoritative state
ApplyAuthoritativeState();
}
diff --git a/Documentation~/index.md b/Documentation~/index.md
index e03233b..aad2141 100644
--- a/Documentation~/index.md
+++ b/Documentation~/index.md
@@ -7,8 +7,8 @@ Netcode for GameObjects is a Unity package that provides networking capabilities
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
- [Documentation](https://docs-multiplayer.unity3d.com/netcode/current/about)
-- [Installation](https://docs-multiplayer.unity3d.com/netcode/current/migration/install)
-- [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro)
+- [Installation](https://docs-multiplayer.unity3d.com/netcode/current/installation)
+- [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/get-started-ngo)
- [API Reference](https://docs-multiplayer.unity3d.com/netcode/current/api/introduction)
# Technical details
@@ -32,4 +32,4 @@ On the following runtime platforms:
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|August 5, 2021|Update product/package name|
|September 9,2021|Updated the links and name of the file.|
-|April 20, 2022|Updated links|
\ No newline at end of file
+|April 20, 2022|Updated links|
diff --git a/Editor/CodeGen/CodeGenHelpers.cs b/Editor/CodeGen/CodeGenHelpers.cs
index 7571857..473e84f 100644
--- a/Editor/CodeGen/CodeGenHelpers.cs
+++ b/Editor/CodeGen/CodeGenHelpers.cs
@@ -10,6 +10,7 @@ using Unity.Collections;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEngine;
+using Object = System.Object;
namespace Unity.Netcode.Editor.CodeGen
{
@@ -112,6 +113,60 @@ namespace Unity.Netcode.Editor.CodeGen
return name;
}
+ public static TypeReference MakeGenericType(this TypeReference self, params TypeReference[] arguments)
+ {
+ if (self.GenericParameters.Count != arguments.Length)
+ {
+ throw new ArgumentException();
+ }
+
+ var instance = new GenericInstanceType(self);
+ foreach (var argument in arguments)
+ {
+ instance.GenericArguments.Add(argument);
+ }
+
+ return instance;
+ }
+
+ public static MethodReference MakeGeneric(this MethodReference self, params TypeReference[] arguments)
+ {
+ var reference = new MethodReference(self.Name, self.ReturnType)
+ {
+ DeclaringType = self.DeclaringType.MakeGenericType(arguments),
+ HasThis = self.HasThis,
+ ExplicitThis = self.ExplicitThis,
+ CallingConvention = self.CallingConvention,
+ };
+
+ foreach (var parameter in self.Parameters)
+ {
+ reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
+ }
+
+ foreach (var generic_parameter in self.GenericParameters)
+ {
+ reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
+ }
+
+ return reference;
+ }
+
+ public static bool IsSubclassOf(this TypeReference typeReference, TypeReference baseClass)
+ {
+ var type = typeReference.Resolve();
+ if (type?.BaseType == null || type.BaseType.Name == nameof(Object))
+ {
+ return false;
+ }
+
+ if (type.BaseType.Resolve() == baseClass.Resolve())
+ {
+ return true;
+ }
+
+ return type.BaseType.IsSubclassOf(baseClass);
+ }
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
{
diff --git a/Editor/CodeGen/INetworkMessageILPP.cs b/Editor/CodeGen/INetworkMessageILPP.cs
index 10da1b0..0f7149c 100644
--- a/Editor/CodeGen/INetworkMessageILPP.cs
+++ b/Editor/CodeGen/INetworkMessageILPP.cs
@@ -101,31 +101,28 @@ namespace Unity.Netcode.Editor.CodeGen
private ModuleDefinition m_NetcodeModule;
private PostProcessorAssemblyResolver m_AssemblyResolver;
- private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef;
- private MethodReference m_MessagingSystem_CreateMessageAndGetVersion_MethodRef;
- private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef;
- private MethodReference m_MessagingSystem_MessageHandler_Constructor_TypeRef;
- private MethodReference m_MessagingSystem_VersionGetter_Constructor_TypeRef;
+ private MethodReference m_MessageManager_ReceiveMessage_MethodRef;
+ private MethodReference m_MessageManager_CreateMessageAndGetVersion_MethodRef;
+ private TypeReference m_MessageManager_MessageWithHandler_TypeRef;
+ private MethodReference m_MessageManager_MessageHandler_Constructor_TypeRef;
+ private MethodReference m_MessageManager_VersionGetter_Constructor_TypeRef;
private FieldReference m_ILPPMessageProvider___network_message_types_FieldRef;
- private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef;
- private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef;
- private FieldReference m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef;
+ private FieldReference m_MessageManager_MessageWithHandler_MessageType_FieldRef;
+ private FieldReference m_MessageManager_MessageWithHandler_Handler_FieldRef;
+ private FieldReference m_MessageManager_MessageWithHandler_GetVersion_FieldRef;
private MethodReference m_Type_GetTypeFromHandle_MethodRef;
private MethodReference m_List_Add_MethodRef;
- private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage);
- private const string k_CreateMessageAndGetVersionName = nameof(MessagingSystem.CreateMessageAndGetVersion);
+ private const string k_ReceiveMessageName = nameof(NetworkMessageManager.ReceiveMessage);
+ private const string k_CreateMessageAndGetVersionName = nameof(NetworkMessageManager.CreateMessageAndGetVersion);
private bool ImportReferences(ModuleDefinition moduleDefinition)
{
// Different environments seem to have different situations...
- // Some have these definitions in netstandard.dll...
- // some seem to have them elsewhere...
- // Since they're standard .net classes they're not going to cause
- // the same issues as referencing other assemblies, in theory, since
- // the definitions should be standard and consistent across platforms
- // (i.e., there's no #if UNITY_EDITOR in them that could create
- // invalid IL code)
+ // Some have these definitions in netstandard.dll, some seem to have them elsewhere...
+ // Since they're standard .net classes they're not going to cause the same issues as referencing other assemblies,
+ // in theory, since the definitions should be standard and consistent across platforms
+ // (i.e., there's no #if UNITY_EDITOR in them that could create invalid IL code)
TypeDefinition typeTypeDef = moduleDefinition.ImportReference(typeof(Type)).Resolve();
TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve();
@@ -133,22 +130,22 @@ namespace Unity.Netcode.Editor.CodeGen
TypeDefinition versionGetterTypeDef = null;
TypeDefinition messageWithHandlerTypeDef = null;
TypeDefinition ilppMessageProviderTypeDef = null;
- TypeDefinition messagingSystemTypeDef = null;
+ TypeDefinition messageManagerSystemTypeDef = null;
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
{
- if (messageHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageHandler))
+ if (messageHandlerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkMessageManager.MessageHandler))
{
messageHandlerTypeDef = netcodeTypeDef;
continue;
}
- if (versionGetterTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.VersionGetter))
+ if (versionGetterTypeDef == null && netcodeTypeDef.Name == nameof(NetworkMessageManager.VersionGetter))
{
versionGetterTypeDef = netcodeTypeDef;
continue;
}
- if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageWithHandler))
+ if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkMessageManager.MessageWithHandler))
{
messageWithHandlerTypeDef = netcodeTypeDef;
continue;
@@ -160,29 +157,29 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
- if (messagingSystemTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem))
+ if (messageManagerSystemTypeDef == null && netcodeTypeDef.Name == nameof(NetworkMessageManager))
{
- messagingSystemTypeDef = netcodeTypeDef;
+ messageManagerSystemTypeDef = netcodeTypeDef;
continue;
}
}
- m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(messageHandlerTypeDef.GetConstructors().First());
- m_MessagingSystem_VersionGetter_Constructor_TypeRef = moduleDefinition.ImportReference(versionGetterTypeDef.GetConstructors().First());
+ m_MessageManager_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(messageHandlerTypeDef.GetConstructors().First());
+ m_MessageManager_VersionGetter_Constructor_TypeRef = moduleDefinition.ImportReference(versionGetterTypeDef.GetConstructors().First());
- m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerTypeDef);
+ m_MessageManager_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerTypeDef);
foreach (var fieldDef in messageWithHandlerTypeDef.Fields)
{
switch (fieldDef.Name)
{
- case nameof(MessagingSystem.MessageWithHandler.MessageType):
- m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldDef);
+ case nameof(NetworkMessageManager.MessageWithHandler.MessageType):
+ m_MessageManager_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
- case nameof(MessagingSystem.MessageWithHandler.Handler):
- m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef);
+ case nameof(NetworkMessageManager.MessageWithHandler.Handler):
+ m_MessageManager_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
- case nameof(MessagingSystem.MessageWithHandler.GetVersion):
- m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef = moduleDefinition.ImportReference(fieldDef);
+ case nameof(NetworkMessageManager.MessageWithHandler.GetVersion):
+ m_MessageManager_MessageWithHandler_GetVersion_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
}
}
@@ -219,15 +216,15 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
- foreach (var methodDef in messagingSystemTypeDef.Methods)
+ foreach (var methodDef in messageManagerSystemTypeDef.Methods)
{
switch (methodDef.Name)
{
case k_ReceiveMessageName:
- m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef);
+ m_MessageManager_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
case k_CreateMessageAndGetVersionName:
- m_MessagingSystem_CreateMessageAndGetVersion_MethodRef = moduleDefinition.ImportReference(methodDef);
+ m_MessageManager_CreateMessageAndGetVersion_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
}
}
@@ -256,27 +253,27 @@ namespace Unity.Netcode.Editor.CodeGen
private void CreateInstructionsToRegisterType(ILProcessor processor, List instructions, TypeReference type, MethodReference receiveMethod, MethodReference versionMethod)
{
- // MessagingSystem.__network_message_types.Add(new MessagingSystem.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive});
- processor.Body.Variables.Add(new VariableDefinition(m_MessagingSystem_MessageWithHandler_TypeRef));
+ // NetworkMessageManager.__network_message_types.Add(new NetworkMessageManager.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive});
+ processor.Body.Variables.Add(new VariableDefinition(m_MessageManager_MessageWithHandler_TypeRef));
int messageWithHandlerLocIdx = processor.Body.Variables.Count - 1;
instructions.Add(processor.Create(OpCodes.Ldsfld, m_ILPPMessageProvider___network_message_types_FieldRef));
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
- instructions.Add(processor.Create(OpCodes.Initobj, m_MessagingSystem_MessageWithHandler_TypeRef));
+ instructions.Add(processor.Create(OpCodes.Initobj, m_MessageManager_MessageWithHandler_TypeRef));
// tmp.MessageType = typeof(type);
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
instructions.Add(processor.Create(OpCodes.Ldtoken, type));
instructions.Add(processor.Create(OpCodes.Call, m_Type_GetTypeFromHandle_MethodRef));
- instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_MessageType_FieldRef));
+ instructions.Add(processor.Create(OpCodes.Stfld, m_MessageManager_MessageWithHandler_MessageType_FieldRef));
// tmp.Handler = MessageHandler.ReceveMessage
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
instructions.Add(processor.Create(OpCodes.Ldnull));
instructions.Add(processor.Create(OpCodes.Ldftn, receiveMethod));
- instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_MessageHandler_Constructor_TypeRef));
- instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_Handler_FieldRef));
+ instructions.Add(processor.Create(OpCodes.Newobj, m_MessageManager_MessageHandler_Constructor_TypeRef));
+ instructions.Add(processor.Create(OpCodes.Stfld, m_MessageManager_MessageWithHandler_Handler_FieldRef));
// tmp.GetVersion = MessageHandler.CreateMessageAndGetVersion
@@ -284,15 +281,15 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(processor.Create(OpCodes.Ldnull));
instructions.Add(processor.Create(OpCodes.Ldftn, versionMethod));
- instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_VersionGetter_Constructor_TypeRef));
- instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef));
+ instructions.Add(processor.Create(OpCodes.Newobj, m_MessageManager_VersionGetter_Constructor_TypeRef));
+ instructions.Add(processor.Create(OpCodes.Stfld, m_MessageManager_MessageWithHandler_GetVersion_FieldRef));
// ILPPMessageProvider.__network_message_types.Add(tmp);
instructions.Add(processor.Create(OpCodes.Ldloc, messageWithHandlerLocIdx));
instructions.Add(processor.Create(OpCodes.Callvirt, m_List_Add_MethodRef));
}
- // Creates a static module constructor (which is executed when the module is loaded) that registers all the message types in the assembly with MessagingSystem.
+ // Creates a static module constructor (which is executed when the module is loaded) that registers all the message types in the assembly with NetworkMessageManager.
// 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
@@ -310,9 +307,9 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var type in networkMessageTypes)
{
- var receiveMethod = new GenericInstanceMethod(m_MessagingSystem_ReceiveMessage_MethodRef);
+ var receiveMethod = new GenericInstanceMethod(m_MessageManager_ReceiveMessage_MethodRef);
receiveMethod.GenericArguments.Add(type);
- var versionMethod = new GenericInstanceMethod(m_MessagingSystem_CreateMessageAndGetVersion_MethodRef);
+ var versionMethod = new GenericInstanceMethod(m_MessageManager_CreateMessageAndGetVersion_MethodRef);
versionMethod.GenericArguments.Add(type);
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod, versionMethod);
}
diff --git a/Editor/CodeGen/NetworkBehaviourILPP.cs b/Editor/CodeGen/NetworkBehaviourILPP.cs
index b57cfa4..b7ad88c 100644
--- a/Editor/CodeGen/NetworkBehaviourILPP.cs
+++ b/Editor/CodeGen/NetworkBehaviourILPP.cs
@@ -18,6 +18,8 @@ namespace Unity.Netcode.Editor.CodeGen
internal sealed class NetworkBehaviourILPP : ILPPInterface
{
private const string k_ReadValueMethodName = nameof(FastBufferReader.ReadValueSafe);
+ private const string k_ReadValueInPlaceMethodName = nameof(FastBufferReader.ReadValueSafeInPlace);
+ private const string k_ReadValueTempMethodName = nameof(FastBufferReader.ReadValueSafeTemp);
private const string k_WriteValueMethodName = nameof(FastBufferWriter.WriteValueSafe);
public override ILPPInterface GetInstance() => this;
@@ -166,6 +168,11 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var type in m_WrappedNetworkVariableTypes)
{
+ if (type.Resolve() == null)
+ {
+ continue;
+ }
+
if (IsSpecialCaseType(type))
{
continue;
@@ -177,7 +184,72 @@ namespace Unity.Netcode.Editor.CodeGen
GenericInstanceMethod serializeMethod = null;
GenericInstanceMethod equalityMethod;
- if (type.IsValueType)
+
+ if (type.Resolve().FullName == "Unity.Collections.NativeArray`1")
+ {
+ var wrappedType = ((GenericInstanceType)type).GenericArguments[0];
+ if (IsSpecialCaseType(wrappedType) || wrappedType.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || wrappedType.Resolve().IsEnum || IsMemcpyableType(wrappedType))
+ {
+ serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef);
+ }
+ else if (wrappedType.HasInterface(typeof(INetworkSerializable).FullName))
+ {
+ serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef);
+ }
+ else if (wrappedType.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && wrappedType.HasInterface(k_INativeListBool_FullName))
+ {
+ serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef);
+ }
+
+ if (wrappedType.HasInterface(typeof(IEquatable<>).FullName + "<" + wrappedType.FullName + ">"))
+ {
+ equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableArray_MethodRef);
+ }
+ else
+ {
+ equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef);
+ }
+
+ if (serializeMethod != null)
+ {
+ serializeMethod.GenericArguments.Add(wrappedType);
+ }
+ equalityMethod.GenericArguments.Add(wrappedType);
+ }
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ else if (type.Resolve().FullName == "Unity.Collections.NativeList`1")
+ {
+ var wrappedType = ((GenericInstanceType)type).GenericArguments[0];
+ if (IsSpecialCaseType(wrappedType) || wrappedType.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || wrappedType.Resolve().IsEnum || IsMemcpyableType(wrappedType))
+ {
+ serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyList_MethodRef);
+ }
+ else if (wrappedType.HasInterface(typeof(INetworkSerializable).FullName))
+ {
+ serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef);
+ }
+ else if (wrappedType.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && wrappedType.HasInterface(k_INativeListBool_FullName))
+ {
+ serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringList_MethodRef);
+ }
+
+ if (wrappedType.HasInterface(typeof(IEquatable<>).FullName + "<" + wrappedType.FullName + ">"))
+ {
+ equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef);
+ }
+ else
+ {
+ equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef);
+ }
+
+ if (serializeMethod != null)
+ {
+ serializeMethod.GenericArguments.Add(wrappedType);
+ }
+ equalityMethod.GenericArguments.Add(wrappedType);
+ }
+#endif
+ else if (type.IsValueType)
{
if (type.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || type.Resolve().IsEnum || IsMemcpyableType(type))
{
@@ -200,11 +272,32 @@ namespace Unity.Netcode.Editor.CodeGen
{
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef);
}
+
+ if (serializeMethod != null)
+ {
+ serializeMethod.GenericArguments.Add(type);
+ }
+ equalityMethod.GenericArguments.Add(type);
}
else
{
if (type.HasInterface(typeof(INetworkSerializable).FullName))
{
+ var constructors = type.Resolve().GetConstructors();
+ var hasEmptyConstructor = false;
+ foreach (var constructor in constructors)
+ {
+ if (constructor.Parameters.Count == 0)
+ {
+ hasEmptyConstructor = true;
+ }
+ }
+
+ if (!hasEmptyConstructor)
+ {
+ m_Diagnostics.AddError($"{type} cannot be used in a network variable - Managed {nameof(INetworkSerializable)} instances must meet the `new()` (default empty constructor) constraint.");
+ continue;
+ }
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef);
}
@@ -216,14 +309,18 @@ namespace Unity.Netcode.Editor.CodeGen
{
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef);
}
+
+ if (serializeMethod != null)
+ {
+ serializeMethod.GenericArguments.Add(type);
+ }
+ equalityMethod.GenericArguments.Add(type);
}
if (serializeMethod != null)
{
- serializeMethod.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod)));
}
- equalityMethod.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(equalityMethod)));
}
@@ -251,11 +348,15 @@ namespace Unity.Netcode.Editor.CodeGen
private FieldReference m_NetworkManager_rpc_name_table_FieldRef;
private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef;
private TypeReference m_NetworkBehaviour_TypeRef;
+ private TypeReference m_NetworkVariableBase_TypeRef;
+ private MethodReference m_NetworkVariableBase_Initialize_MethodRef;
+ private MethodReference m_NetworkBehaviour___nameNetworkVariable_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendClientRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendClientRpc_MethodRef;
private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef;
+ private FieldReference m_NetworkBehaviour_NetworkVariableFields_FieldRef;
private MethodReference m_NetworkBehaviour_getNetworkManager_MethodRef;
private MethodReference m_NetworkBehaviour_getOwnerClientId_MethodRef;
private MethodReference m_NetworkHandlerDelegateCtor_MethodRef;
@@ -267,14 +368,37 @@ namespace Unity.Netcode.Editor.CodeGen
private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef;
private TypeReference m_ClientRpcParams_TypeRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef;
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef;
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyList_MethodRef;
+#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef;
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef;
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef;
+#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef;
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef;
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringList_MethodRef;
+#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef;
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableArray_MethodRef;
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef;
+#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef;
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef;
+#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
+ private MethodReference m_ExceptionCtorMethodReference;
+ private MethodReference m_List_NetworkVariableBase_Add;
+
private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef;
@@ -289,6 +413,8 @@ namespace Unity.Netcode.Editor.CodeGen
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef;
+ private MethodReference m_NetworkBehaviour_createNativeList_MethodRef;
+
private TypeReference m_FastBufferWriter_TypeRef;
private readonly Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary();
private readonly List m_FastBufferWriter_ExtensionMethodRefs = new List();
@@ -348,12 +474,18 @@ namespace Unity.Netcode.Editor.CodeGen
private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table);
private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage);
+ private const string k_NetworkBehaviour_NetworkVariableFields = nameof(NetworkBehaviour.NetworkVariableFields);
private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc);
private const string k_NetworkBehaviour_endSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc);
private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc);
private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc);
+ private const string k_NetworkBehaviour___initializeVariables = nameof(NetworkBehaviour.__initializeVariables);
+ private const string k_NetworkBehaviour_createNativeList = nameof(NetworkBehaviour.__createNativeList);
private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager);
private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId);
+ private const string k_NetworkBehaviour___nameNetworkVariable = nameof(NetworkBehaviour.__nameNetworkVariable);
+
+ private const string k_NetworkVariableBase_Initialize = nameof(NetworkVariableBase.Initialize);
private const string k_RpcAttribute_Delivery = nameof(RpcAttribute.Delivery);
private const string k_ServerRpcAttribute_RequireOwnership = nameof(ServerRpcAttribute.RequireOwnership);
@@ -379,6 +511,7 @@ namespace Unity.Netcode.Editor.CodeGen
TypeDefinition networkManagerTypeDef = null;
TypeDefinition networkBehaviourTypeDef = null;
+ TypeDefinition networkVariableBaseTypeDef = null;
TypeDefinition networkHandlerDelegateTypeDef = null;
TypeDefinition rpcParamsTypeDef = null;
TypeDefinition serverRpcParamsTypeDef = null;
@@ -402,6 +535,12 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
+ if (networkVariableBaseTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableBase))
+ {
+ networkVariableBaseTypeDef = netcodeTypeDef;
+ continue;
+ }
+
if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler))
{
networkHandlerDelegateTypeDef = netcodeTypeDef;
@@ -548,6 +687,12 @@ namespace Unity.Netcode.Editor.CodeGen
case k_NetworkBehaviour_endSendClientRpc:
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
+ case k_NetworkBehaviour_createNativeList:
+ m_NetworkBehaviour_createNativeList_MethodRef = moduleDefinition.ImportReference(methodDef);
+ break;
+ case k_NetworkBehaviour___nameNetworkVariable:
+ m_NetworkBehaviour___nameNetworkVariable_MethodRef = moduleDefinition.ImportReference(methodDef);
+ break;
}
}
@@ -558,6 +703,21 @@ namespace Unity.Netcode.Editor.CodeGen
case k_NetworkBehaviour_rpc_exec_stage:
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
+ case k_NetworkBehaviour_NetworkVariableFields:
+ m_NetworkBehaviour_NetworkVariableFields_FieldRef = moduleDefinition.ImportReference(fieldDef);
+ break;
+ }
+ }
+
+
+ m_NetworkVariableBase_TypeRef = moduleDefinition.ImportReference(networkVariableBaseTypeDef);
+ foreach (var methodDef in networkVariableBaseTypeDef.Methods)
+ {
+ switch (methodDef.Name)
+ {
+ case k_NetworkVariableBase_Initialize:
+ m_NetworkVariableBase_Initialize_MethodRef = moduleDefinition.ImportReference(methodDef);
+ break;
}
}
@@ -685,24 +845,69 @@ namespace Unity.Netcode.Editor.CodeGen
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpy):
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef = method;
break;
+ case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpyArray):
+ m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef = method;
+ break;
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpyList):
+ m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyList_MethodRef = method;
+ break;
+#endif
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializable):
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef = method;
break;
+ case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializableArray):
+ m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef = method;
+ break;
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializableList):
+ m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef = method;
+ break;
+#endif
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable):
m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedString):
m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef = method;
break;
+ case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedStringArray):
+ m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef = method;
+ break;
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedStringList):
+ m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringList_MethodRef = method;
+ break;
+#endif
+
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedIEquatable):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatable):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef = method;
break;
+ case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatableArray):
+ m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableArray_MethodRef = method;
+ break;
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatableList):
+ m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef = method;
+ break;
+#endif
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method;
break;
+ case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEqualsArray):
+ m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef = method;
+ break;
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEqualsList):
+ m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef = method;
+ break;
+#endif
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedClassEquals):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef = method;
break;
@@ -785,6 +990,16 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
+ // Standard types are really hard to reliably find using the Mono Cecil way, they resolve differently in Mono vs .NET Core
+ // Importing with typeof() is less dangerous for standard framework types though, so we can just do it
+ var exceptionType = typeof(Exception);
+ var exceptionCtor = exceptionType.GetConstructor(new[] { typeof(string) });
+ m_ExceptionCtorMethodReference = m_MainModule.ImportReference(exceptionCtor);
+
+ var listType = typeof(List);
+ var addMethod = listType.GetMethod(nameof(List.Add), new[] { typeof(NetworkVariableBase) });
+ m_List_NetworkVariableBase_Add = moduleDefinition.ImportReference(addMethod);
+
return true;
}
@@ -931,6 +1146,8 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
+ GenerateVariableInitialization(typeDefinition);
+
if (!typeDefinition.HasGenericParameters && !typeDefinition.IsGenericInstance)
{
var fieldTypes = new List();
@@ -1147,28 +1364,48 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
- var checkType = paramType.Resolve();
+ var checkType = paramType;
if (paramType.IsArray)
{
checkType = ((ArrayType)paramType).ElementType.Resolve();
}
- if ((parameters[0].ParameterType.Resolve() == checkType ||
- (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
+ if (!method.HasGenericParameters)
{
- return method;
- }
+ if (!paramType.IsGenericInstance && (parameters[0].ParameterType.Resolve() == checkType ||
+ (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
+ {
+ return method;
+ }
- if (parameters[0].ParameterType == paramType ||
- (parameters[0].ParameterType == paramType.MakeByReferenceType() && parameters[0].IsIn))
- {
- return method;
+ if (parameters[0].ParameterType == paramType || parameters[0].ParameterType.FullName == paramType.FullName ||
+ (parameters[0].ParameterType == paramType.MakeByReferenceType() && parameters[0].IsIn))
+ {
+ return method;
+ }
}
-
- if (method.HasGenericParameters && method.GenericParameters.Count == 1)
+ else if (method.GenericParameters.Count == 1)
{
+ var resolved = method.Parameters[0].ParameterType.Resolve();
+ if (resolved != null && resolved != paramType.Resolve())
+ {
+ continue;
+ }
if (method.GenericParameters[0].HasConstraints)
{
+ if (paramType.IsGenericInstance && (
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ paramType.Resolve().FullName == "Unity.Collections.NativeList`1" ||
+#endif
+ paramType.Resolve().FullName == "Unity.Collections.NativeArray`1"))
+ {
+ if (method.Parameters[0].ParameterType.Resolve() != paramType.Resolve())
+ {
+ continue;
+ }
+ var instanceType = (GenericInstanceType)paramType;
+ checkType = instanceType.GenericArguments[0];
+ }
var meetsConstraints = true;
foreach (var constraint in method.GenericParameters[0].Constraints)
{
@@ -1201,7 +1438,17 @@ namespace Unity.Netcode.Editor.CodeGen
if (meetsConstraints)
{
var instanceMethod = new GenericInstanceMethod(method);
- if (paramType.IsArray)
+
+ if (paramType.IsGenericInstance && (
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ paramType.Resolve().FullName == "Unity.Collections.NativeList`1" ||
+#endif
+ paramType.Resolve().FullName == "Unity.Collections.NativeArray`1"))
+ {
+ var wrappedType = ((GenericInstanceType)paramType).GenericArguments[0];
+ instanceMethod.GenericArguments.Add(wrappedType);
+ }
+ else if (paramType.IsArray)
{
instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType);
}
@@ -1310,9 +1557,9 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
- if (!parameters[0].IsOut)
+ if (!parameters[0].IsOut && !parameters[0].ParameterType.IsByReference)
{
- return null;
+ continue;
}
var methodParam = ((ByReferenceType)parameters[0].ParameterType).ElementType;
@@ -1322,24 +1569,56 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
- var checkType = paramType.Resolve();
+ var checkType = (TypeReference)paramType.Resolve();
if (paramType.IsArray)
{
checkType = ((ArrayType)paramType).ElementType.Resolve();
}
- if (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve())
+ if (!method.HasGenericParameters)
{
- return method;
- }
+ if (!paramType.IsGenericInstance && (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve()))
+ {
+ return method;
+ }
- if (methodParam.Resolve() == paramType || methodParam.Resolve() == paramType.MakeByReferenceType().Resolve())
- {
- return method;
+ if (methodParam.Resolve() == paramType || methodParam.FullName == paramType.FullName)
+ {
+ return method;
+ }
}
-
- if (method.HasGenericParameters && method.GenericParameters.Count == 1)
+ else if (method.GenericParameters.Count == 1)
{
+ var resolved = method.Parameters[0].ParameterType.Resolve();
+ if (resolved != null && resolved != paramType.Resolve())
+ {
+ continue;
+ }
+ if (paramType.IsGenericInstance && (
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ paramType.Resolve().FullName == "Unity.Collections.NativeList`1" ||
+#endif
+ paramType.Resolve().FullName == "Unity.Collections.NativeArray`1"))
+ {
+ if (method.Name == "OnSendGlobalCounterClientRpc")
+ {
+ m_Diagnostics.AddWarning(
+ $"{method}: {method.Parameters[0].ParameterType} | {paramType}"
+ );
+ }
+ if (method.Parameters[0].ParameterType.Resolve() != paramType.Resolve())
+ {
+ if (method.Name == "OnSendGlobalCounterClientRpc")
+ {
+ m_Diagnostics.AddWarning(
+ $"{method}: Not suitable"
+ );
+ }
+ continue;
+ }
+ var instanceType = (GenericInstanceType)paramType;
+ checkType = instanceType.GenericArguments[0];
+ }
if (method.GenericParameters[0].HasConstraints)
{
var meetsConstraints = true;
@@ -1376,7 +1655,16 @@ namespace Unity.Netcode.Editor.CodeGen
if (meetsConstraints)
{
var instanceMethod = new GenericInstanceMethod(method);
- if (paramType.IsArray)
+ if (paramType.IsGenericInstance && (
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ paramType.Resolve().FullName == "Unity.Collections.NativeList`1" ||
+#endif
+ paramType.Resolve().FullName == "Unity.Collections.NativeArray`1"))
+ {
+ var wrappedType = ((GenericInstanceType)paramType).GenericArguments[0];
+ instanceMethod.GenericArguments.Add(wrappedType);
+ }
+ else if (paramType.IsArray)
{
instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType);
}
@@ -1384,7 +1672,6 @@ namespace Unity.Netcode.Editor.CodeGen
{
instanceMethod.GenericArguments.Add(paramType);
}
-
return instanceMethod;
}
}
@@ -1446,7 +1733,22 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
- var typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
+ MethodReference typeMethod;
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ if (paramType.Resolve().FullName == "Unity.Collections.NativeList`1")
+ {
+ typeMethod = GetFastBufferReaderReadMethod(k_ReadValueInPlaceMethodName, paramType);
+ }
+ else
+#endif
+ if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1")
+ {
+ typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType);
+ }
+ else
+ {
+ typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
+ }
if (typeMethod != null)
{
methodRef = m_MainModule.ImportReference(typeMethod);
@@ -1786,7 +2088,7 @@ namespace Unity.Netcode.Editor.CodeGen
}
else
{
- m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
+ m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
continue;
}
@@ -1889,6 +2191,132 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
}
+ private void GenerateVariableInitialization(TypeDefinition type)
+ {
+ foreach (var methodDefinition in type.Methods)
+ {
+ if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
+ {
+ // If this hits, we've already generated the method for this class because a child class got processed first.
+ return;
+ }
+ }
+
+ var method = new MethodDefinition(
+ k_NetworkBehaviour___initializeVariables,
+ MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ m_MainModule.TypeSystem.Void);
+
+ var processor = method.Body.GetILProcessor();
+
+ method.Body.Variables.Add(new VariableDefinition(m_MainModule.TypeSystem.Boolean));
+
+ processor.Emit(OpCodes.Nop);
+
+ foreach (var fieldDefinition in type.Fields)
+ {
+ FieldReference field = fieldDefinition;
+ if (type.HasGenericParameters)
+ {
+ var genericType = new GenericInstanceType(fieldDefinition.DeclaringType);
+ foreach (var parameter in fieldDefinition.DeclaringType.GenericParameters)
+ {
+ genericType.GenericArguments.Add(parameter);
+ }
+ field = new FieldReference(fieldDefinition.Name, fieldDefinition.FieldType, genericType);
+ }
+ if (!field.FieldType.IsArray && !field.FieldType.Resolve().IsArray && field.FieldType.IsSubclassOf(m_NetworkVariableBase_TypeRef))
+ {
+ // if({variable} == null) {
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Ldfld, field);
+ processor.Emit(OpCodes.Ldnull);
+ processor.Emit(OpCodes.Ceq);
+ processor.Emit(OpCodes.Stloc_0);
+ processor.Emit(OpCodes.Ldloc_0);
+
+ var afterThrowInstruction = processor.Create(OpCodes.Nop);
+
+ processor.Emit(OpCodes.Brfalse, afterThrowInstruction);
+
+ // throw new Exception("...");
+ processor.Emit(OpCodes.Nop);
+ processor.Emit(OpCodes.Ldstr, $"{type.Name}.{field.Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
+ processor.Emit(OpCodes.Newobj, m_ExceptionCtorMethodReference);
+ processor.Emit(OpCodes.Throw);
+
+ // }
+ processor.Append(afterThrowInstruction);
+
+ // {variable}.Initialize(this);
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Ldfld, field);
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Callvirt, m_NetworkVariableBase_Initialize_MethodRef);
+
+ // __nameNetworkVariable({variable}, "{variable}");
+ processor.Emit(OpCodes.Nop);
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Ldfld, field);
+ processor.Emit(OpCodes.Ldstr, field.Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty));
+ processor.Emit(OpCodes.Call, m_NetworkBehaviour___nameNetworkVariable_MethodRef);
+
+ // NetworkVariableFields.Add({variable});
+ processor.Emit(OpCodes.Nop);
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Ldfld, m_NetworkBehaviour_NetworkVariableFields_FieldRef);
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Ldfld, field);
+ processor.Emit(OpCodes.Callvirt, m_List_NetworkVariableBase_Add);
+ }
+ }
+
+ // Find the base method...
+ MethodReference initializeVariablesBaseReference = null;
+ foreach (var methodDefinition in type.BaseType.Resolve().Methods)
+ {
+ if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
+ {
+ initializeVariablesBaseReference = m_MainModule.ImportReference(methodDefinition);
+ break;
+ }
+ }
+
+ if (initializeVariablesBaseReference == null)
+ {
+ // If we couldn't find it, we have to go ahead and add it.
+ // The base class could be in another assembly... that's ok, this won't
+ // actually save but it'll generate the same method the same way later,
+ // so this at least allows us to reference it.
+ GenerateVariableInitialization(type.BaseType.Resolve());
+ foreach (var methodDefinition in type.BaseType.Resolve().Methods)
+ {
+ if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
+ {
+ initializeVariablesBaseReference = m_MainModule.ImportReference(methodDefinition);
+ break;
+ }
+ }
+ }
+
+ if (type.BaseType.Resolve().HasGenericParameters)
+ {
+ var baseTypeInstance = (GenericInstanceType)type.BaseType;
+ initializeVariablesBaseReference = initializeVariablesBaseReference.MakeGeneric(baseTypeInstance.GenericArguments.ToArray());
+ }
+
+ // base.__initializeVariables();
+ processor.Emit(OpCodes.Nop);
+ processor.Emit(OpCodes.Ldarg_0);
+ processor.Emit(OpCodes.Call, initializeVariablesBaseReference);
+ processor.Emit(OpCodes.Nop);
+
+ processor.Emit(OpCodes.Ret);
+
+ type.Methods.Add(method);
+ }
+
private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute, uint rpcMethodId)
{
var typeSystem = methodDefinition.Module.TypeSystem;
@@ -2048,6 +2476,28 @@ namespace Unity.Netcode.Editor.CodeGen
processor.Emit(OpCodes.Brfalse, jumpInstruction);
}
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ if (paramType.IsGenericInstance && paramType.Resolve().FullName == "Unity.Collections.NativeList`1")
+ {
+ // var list = NetworkBehaviour.__createNativeList();
+
+ // This simplifies things - easier to call __createNativeList() and have the implementation in C#
+ // than to try to actually construct a NativeList in IL. This is also more future-proof.
+
+ // Unlike other types, NativeList<> calls ReadValueSafeInPlace instead of ReadValueSafe.
+ // FastBufferReader doesn't support a non-in-place deserializer for NativeList in order to
+ // avoid users using it without realizing the allocation overhead that would cost. In-place
+ // is more efficient when an existing value exists, and when it doesn't, it's easy to create one,
+ // which is what we do here.
+
+ var method = new GenericInstanceMethod(m_NetworkBehaviour_createNativeList_MethodRef);
+ var genericParam = (GenericInstanceType)paramType;
+ method.GenericArguments.Add(genericParam.GenericArguments[0]);
+ processor.Emit(OpCodes.Call, method);
+ processor.Emit(OpCodes.Stloc, localIndex);
+ }
+#endif
+
var foundMethodRef = GetReadMethodForParameter(paramType, out var methodRef);
if (foundMethodRef)
{
@@ -2100,7 +2550,7 @@ namespace Unity.Netcode.Editor.CodeGen
}
else
{
- m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
+ m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to deserialize {paramType}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
continue;
}
diff --git a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs
index d16f10d..7316664 100644
--- a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs
+++ b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs
@@ -112,7 +112,7 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var fieldDefinition in typeDefinition.Fields)
{
- if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage))
+ if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage) || fieldDefinition.Name == nameof(NetworkBehaviour.NetworkVariableFields))
{
fieldDefinition.IsFamily = true;
}
@@ -123,7 +123,10 @@ namespace Unity.Netcode.Editor.CodeGen
if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
- methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc))
+ methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) ||
+ methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) ||
+ methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) ||
+ methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList))
{
methodDefinition.IsFamily = true;
}
diff --git a/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs b/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs
new file mode 100644
index 0000000..b8f3b7f
--- /dev/null
+++ b/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs
@@ -0,0 +1,30 @@
+using UnityEditor;
+using UnityEngine;
+
+namespace Unity.Netcode.Editor.Configuration
+{
+ [FilePath("ProjectSettings/NetcodeForGameObjects.asset", FilePathAttribute.Location.ProjectFolder)]
+ public class NetcodeForGameObjectsProjectSettings : ScriptableSingleton
+ {
+ internal static readonly string DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset";
+ [SerializeField] public string NetworkPrefabsPath = DefaultNetworkPrefabsPath;
+ public string TempNetworkPrefabsPath;
+
+ private void OnEnable()
+ {
+ if (NetworkPrefabsPath == "")
+ {
+ NetworkPrefabsPath = DefaultNetworkPrefabsPath;
+ }
+ TempNetworkPrefabsPath = NetworkPrefabsPath;
+ }
+
+ [SerializeField]
+ public bool GenerateDefaultNetworkPrefabs;
+
+ internal void SaveSettings()
+ {
+ Save(true);
+ }
+ }
+}
diff --git a/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs.meta b/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs.meta
new file mode 100644
index 0000000..de788ad
--- /dev/null
+++ b/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2727d53a542a4c1aa312905c3a02d807
+timeCreated: 1685564945
\ No newline at end of file
diff --git a/Editor/Configuration/NetcodeForGameObjectsSettings.cs b/Editor/Configuration/NetcodeForGameObjectsSettings.cs
index 39ce211..4fc4b0c 100644
--- a/Editor/Configuration/NetcodeForGameObjectsSettings.cs
+++ b/Editor/Configuration/NetcodeForGameObjectsSettings.cs
@@ -1,6 +1,4 @@
using UnityEditor;
-using UnityEngine;
-
namespace Unity.Netcode.Editor.Configuration
{
@@ -39,15 +37,4 @@ namespace Unity.Netcode.Editor.Configuration
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting);
}
}
-
- [FilePath("ProjectSettings/NetcodeForGameObjects.settings", FilePathAttribute.Location.ProjectFolder)]
- internal class NetcodeForGameObjectsProjectSettings : ScriptableSingleton
- {
- [SerializeField] public bool GenerateDefaultNetworkPrefabs = true;
-
- internal void SaveSettings()
- {
- Save(true);
- }
- }
}
diff --git a/Editor/Configuration/NetcodeSettingsProvider.cs b/Editor/Configuration/NetcodeSettingsProvider.cs
index 08bbda7..ce8023b 100644
--- a/Editor/Configuration/NetcodeSettingsProvider.cs
+++ b/Editor/Configuration/NetcodeSettingsProvider.cs
@@ -1,5 +1,9 @@
+using System.Collections.Generic;
+using System.IO;
using UnityEditor;
using UnityEngine;
+using Directory = UnityEngine.Windows.Directory;
+using File = UnityEngine.Windows.File;
namespace Unity.Netcode.Editor.Configuration
{
@@ -20,11 +24,60 @@ namespace Unity.Netcode.Editor.Configuration
label = "Netcode for GameObjects",
keywords = new[] { "netcode", "editor" },
guiHandler = OnGuiHandler,
+ deactivateHandler = OnDeactivate
};
return provider;
}
+ private static void OnDeactivate()
+ {
+ var settings = NetcodeForGameObjectsProjectSettings.instance;
+ if (settings.TempNetworkPrefabsPath != settings.NetworkPrefabsPath)
+ {
+ var newPath = settings.TempNetworkPrefabsPath;
+ if (newPath == "")
+ {
+ newPath = NetcodeForGameObjectsProjectSettings.DefaultNetworkPrefabsPath;
+ settings.TempNetworkPrefabsPath = newPath;
+ }
+ var oldPath = settings.NetworkPrefabsPath;
+ settings.NetworkPrefabsPath = settings.TempNetworkPrefabsPath;
+ var dirName = Path.GetDirectoryName(newPath);
+ if (!Directory.Exists(dirName))
+ {
+ var dirs = dirName.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
+ var dirsQueue = new Queue(dirs);
+ var parent = dirsQueue.Dequeue();
+ while (dirsQueue.Count != 0)
+ {
+ var child = dirsQueue.Dequeue();
+ var together = Path.Combine(parent, child);
+ if (!Directory.Exists(together))
+ {
+ AssetDatabase.CreateFolder(parent, child);
+ }
+
+ parent = together;
+ }
+ }
+
+ if (Directory.Exists(dirName))
+ {
+ if (File.Exists(oldPath))
+ {
+ AssetDatabase.MoveAsset(oldPath, newPath);
+ if (File.Exists(oldPath))
+ {
+ File.Delete(oldPath);
+ }
+ AssetDatabase.Refresh();
+ }
+ }
+ settings.SaveSettings();
+ }
+ }
+
internal static NetcodeSettingsLabel NetworkObjectsSectionLabel;
internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle;
@@ -70,6 +123,7 @@ namespace Unity.Netcode.Editor.Configuration
var multiplayerToolsTipStatus = NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
var settings = NetcodeForGameObjectsProjectSettings.instance;
var generateDefaultPrefabs = settings.GenerateDefaultNetworkPrefabs;
+ var networkPrefabsPath = settings.TempNetworkPrefabsPath;
EditorGUI.BeginChangeCheck();
@@ -97,6 +151,7 @@ namespace Unity.Netcode.Editor.Configuration
{
GUILayout.BeginVertical("Box");
const string generateNetworkPrefabsString = "Generate Default Network Prefabs List";
+ const string networkPrefabsLocationString = "Default Network Prefabs List path";
if (s_MaxLabelWidth == 0)
{
@@ -114,6 +169,14 @@ namespace Unity.Netcode.Editor.Configuration
"to date with all NetworkObject prefabs."),
generateDefaultPrefabs,
GUILayout.Width(s_MaxLabelWidth + 20));
+
+ GUI.SetNextControlName("Location");
+ networkPrefabsPath = EditorGUILayout.TextField(
+ new GUIContent(
+ networkPrefabsLocationString,
+ "The path to the asset the default NetworkPrefabList object should be stored in."),
+ networkPrefabsPath,
+ GUILayout.Width(s_MaxLabelWidth + 270));
GUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
@@ -123,6 +186,7 @@ namespace Unity.Netcode.Editor.Configuration
NetcodeForGameObjectsEditorSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
settings.GenerateDefaultNetworkPrefabs = generateDefaultPrefabs;
+ settings.TempNetworkPrefabsPath = networkPrefabsPath;
settings.SaveSettings();
}
}
diff --git a/Editor/Configuration/NetworkPrefabProcessor.cs b/Editor/Configuration/NetworkPrefabProcessor.cs
index 631cee1..879a8c3 100644
--- a/Editor/Configuration/NetworkPrefabProcessor.cs
+++ b/Editor/Configuration/NetworkPrefabProcessor.cs
@@ -9,16 +9,15 @@ namespace Unity.Netcode.Editor.Configuration
///
public class NetworkPrefabProcessor : AssetPostprocessor
{
- private static string s_DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset";
public static string DefaultNetworkPrefabsPath
{
get
{
- return s_DefaultNetworkPrefabsPath;
+ return NetcodeForGameObjectsProjectSettings.instance.NetworkPrefabsPath;
}
internal set
{
- s_DefaultNetworkPrefabsPath = value;
+ NetcodeForGameObjectsProjectSettings.instance.NetworkPrefabsPath = value;
// Force a recache of the prefab list
s_PrefabsList = null;
}
diff --git a/Editor/NetworkBehaviourEditor.cs b/Editor/NetworkBehaviourEditor.cs
index 6a34649..239d107 100644
--- a/Editor/NetworkBehaviourEditor.cs
+++ b/Editor/NetworkBehaviourEditor.cs
@@ -41,13 +41,11 @@ namespace Unity.Netcode.Editor
{
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
- Debug.Log($"Adding NetworkVariable {fields[i].Name}");
}
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkList<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
{
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
- Debug.Log($"Adding NetworkList {fields[i].Name}");
}
}
}
@@ -81,7 +79,25 @@ namespace Unity.Netcode.Editor
EditorGUILayout.BeginHorizontal();
if (genericType.IsValueType)
{
- var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkContainerValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
+ var isEquatable = false;
+ foreach (var iface in genericType.GetInterfaces())
+ {
+ if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IEquatable<>))
+ {
+ isEquatable = true;
+ }
+ }
+
+ MethodInfo method;
+ if (isEquatable)
+ {
+ method = typeof(NetworkBehaviourEditor).GetMethod(nameof(RenderNetworkContainerValueTypeIEquatable), BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
+ }
+ else
+ {
+ method = typeof(NetworkBehaviourEditor).GetMethod(nameof(RenderNetworkContainerValueType), BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
+ }
+
var genericMethod = method.MakeGenericMethod(genericType);
genericMethod.Invoke(this, new[] { (object)index });
}
@@ -94,7 +110,23 @@ namespace Unity.Netcode.Editor
}
}
- private void RenderNetworkContainerValueType(int index) where T : unmanaged, IEquatable
+ private void RenderNetworkContainerValueType(int index) where T : unmanaged
+ {
+ try
+ {
+ var networkVariable = (NetworkVariable)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
+ RenderNetworkVariableValueType(index, networkVariable);
+ }
+ catch (Exception e)
+ {
+ Debug.Log(e);
+ throw;
+ }
+
+ EditorGUILayout.EndHorizontal();
+ }
+
+ private void RenderNetworkContainerValueTypeIEquatable(int index) where T : unmanaged, IEquatable
{
try
{
@@ -240,7 +272,7 @@ namespace Unity.Netcode.Editor
bool expanded = true;
while (property.NextVisible(expanded))
{
- if (m_NetworkVariableNames.Contains(property.name))
+ if (m_NetworkVariableNames.Contains(ObjectNames.NicifyVariableName(property.name)))
{
// Skip rendering of NetworkVars, they have special rendering
continue;
diff --git a/Editor/NetworkManagerEditor.cs b/Editor/NetworkManagerEditor.cs
index be5e6a3..e4acb69 100644
--- a/Editor/NetworkManagerEditor.cs
+++ b/Editor/NetworkManagerEditor.cs
@@ -339,7 +339,7 @@ namespace Unity.Netcode.Editor
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
const string openDocsButtonText = "Open Docs";
const string dismissButtonText = "Dismiss";
- const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools";
+ const string targetUrl = "https://docs-multiplayer.unity3d.com/tools/current/install-tools";
const string infoIconName = "console.infoicon";
if (NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() != 0)
diff --git a/Runtime/Configuration/NetworkConfig.cs b/Runtime/Configuration/NetworkConfig.cs
index c6454af..5970ab1 100644
--- a/Runtime/Configuration/NetworkConfig.cs
+++ b/Runtime/Configuration/NetworkConfig.cs
@@ -156,7 +156,7 @@ namespace Unity.Netcode
public string ToBase64()
{
NetworkConfig config = this;
- var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
+ var writer = new FastBufferWriter(1024, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(config.ProtocolVersion);
@@ -228,7 +228,7 @@ namespace Unity.Netcode
return m_ConfigHash.Value;
}
- var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, int.MaxValue);
+ var writer = new FastBufferWriter(1024, Allocator.Temp, int.MaxValue);
using (writer)
{
writer.WriteValueSafe(ProtocolVersion);
diff --git a/Runtime/Configuration/NetworkPrefab.cs b/Runtime/Configuration/NetworkPrefab.cs
index dbb1d91..9cc2158 100644
--- a/Runtime/Configuration/NetworkPrefab.cs
+++ b/Runtime/Configuration/NetworkPrefab.cs
@@ -12,10 +12,12 @@ namespace Unity.Netcode
/// No oeverride is present
///
None,
+
///
/// Override the prefab when the given SourcePrefabToOverride is requested
///
Prefab,
+
///
/// Override the prefab when the given SourceHashToOverride is requested
/// Used in situations where the server assets do not exist in client builds
@@ -71,19 +73,23 @@ namespace Unity.Netcode
switch (Override)
{
case NetworkPrefabOverride.None:
- if (Prefab != null && Prefab.TryGetComponent(out NetworkObject no))
{
- return no.GlobalObjectIdHash;
- }
+ if (Prefab != null && Prefab.TryGetComponent(out NetworkObject networkObject))
+ {
+ return networkObject.GlobalObjectIdHash;
+ }
- throw new InvalidOperationException("Prefab field isn't set or isn't a Network Object");
+ throw new InvalidOperationException($"Prefab field is not set or is not a {nameof(NetworkObject)}");
+ }
case NetworkPrefabOverride.Prefab:
- if (SourcePrefabToOverride != null && SourcePrefabToOverride.TryGetComponent(out no))
{
- return no.GlobalObjectIdHash;
- }
+ if (SourcePrefabToOverride != null && SourcePrefabToOverride.TryGetComponent(out NetworkObject networkObject))
+ {
+ return networkObject.GlobalObjectIdHash;
+ }
- throw new InvalidOperationException("Source Prefab field isn't set or isn't a Network Object");
+ throw new InvalidOperationException($"Source Prefab field is not set or is not a {nameof(NetworkObject)}");
+ }
case NetworkPrefabOverride.Hash:
return SourceHashToOverride;
default:
@@ -102,12 +108,14 @@ namespace Unity.Netcode
return 0;
case NetworkPrefabOverride.Prefab:
case NetworkPrefabOverride.Hash:
- if (OverridingTargetPrefab != null && OverridingTargetPrefab.TryGetComponent(out NetworkObject no))
{
- return no.GlobalObjectIdHash;
- }
+ if (OverridingTargetPrefab != null && OverridingTargetPrefab.TryGetComponent(out NetworkObject networkObject))
+ {
+ return networkObject.GlobalObjectIdHash;
+ }
- throw new InvalidOperationException("Target Prefab field isn't set or isn't a Network Object");
+ throw new InvalidOperationException($"Target Prefab field is not set or is not a {nameof(NetworkObject)}");
+ }
default:
throw new ArgumentOutOfRangeException();
}
@@ -130,9 +138,9 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
- NetworkLog.LogWarning($"{NetworkManager.PrefabDebugHelper(this)} is missing " +
- $"a {nameof(NetworkObject)} component (entry will be ignored).");
+ NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} is missing a {nameof(NetworkObject)} component (entry will be ignored).");
}
+
return false;
}
@@ -148,9 +156,9 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
- NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(SourceHashToOverride)} is zero " +
- "(entry will be ignored).");
+ NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(SourceHashToOverride)} is zero (entry will be ignored).");
}
+
return false;
}
@@ -178,9 +186,9 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
- NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({SourcePrefabToOverride.name}) " +
- $"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
+ NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({SourcePrefabToOverride.name}) is missing a {nameof(NetworkObject)} component (entry will be ignored).");
}
+
return false;
}
@@ -195,6 +203,7 @@ namespace Unity.Netcode
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(OverridingTargetPrefab)} is null!");
}
+
switch (Override)
{
case NetworkPrefabOverride.Hash:
@@ -208,8 +217,10 @@ namespace Unity.Netcode
break;
}
}
+
return false;
}
+
return true;
}
diff --git a/Runtime/Configuration/NetworkPrefabs.cs b/Runtime/Configuration/NetworkPrefabs.cs
index 1dae3ce..e47dcc1 100644
--- a/Runtime/Configuration/NetworkPrefabs.cs
+++ b/Runtime/Configuration/NetworkPrefabs.cs
@@ -38,10 +38,15 @@ namespace Unity.Netcode
[NonSerialized]
private List m_Prefabs = new List();
+ [NonSerialized]
+ private List m_RuntimeAddedPrefabs = new List();
+
private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
{
if (AddPrefabRegistration(networkPrefab))
{
+ // Don't add this to m_RuntimeAddedPrefabs
+ // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
m_Prefabs.Add(networkPrefab);
}
}
@@ -67,8 +72,6 @@ namespace Unity.Netcode
list.OnAdd -= AddTriggeredByNetworkPrefabList;
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
}
-
- NetworkPrefabsLists.Clear();
}
///
@@ -77,13 +80,7 @@ namespace Unity.Netcode
///
public void Initialize(bool warnInvalid = true)
{
- if (NetworkPrefabsLists.Count != 0 && m_Prefabs.Count > 0)
- {
- NetworkLog.LogWarning("Runtime Network Prefabs was not empty at initialization time. Network " +
- "Prefab registrations made before initialization will be replaced by NetworkPrefabsList.");
- m_Prefabs.Clear();
- }
-
+ m_Prefabs.Clear();
foreach (var list in NetworkPrefabsLists)
{
list.OnAdd += AddTriggeredByNetworkPrefabList;
@@ -93,7 +90,7 @@ namespace Unity.Netcode
NetworkPrefabOverrideLinks.Clear();
OverrideToNetworkPrefab.Clear();
- var prefabs = NetworkPrefabsLists.Count != 0 ? new List() : m_Prefabs;
+ var prefabs = new List();
if (NetworkPrefabsLists.Count != 0)
{
@@ -126,6 +123,18 @@ namespace Unity.Netcode
}
}
+ foreach (var networkPrefab in m_RuntimeAddedPrefabs)
+ {
+ if (AddPrefabRegistration(networkPrefab))
+ {
+ m_Prefabs.Add(networkPrefab);
+ }
+ else
+ {
+ removeList?.Add(networkPrefab);
+ }
+ }
+
// Clear out anything that is invalid or not used
if (removeList?.Count > 0)
{
@@ -152,6 +161,7 @@ namespace Unity.Netcode
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
+ m_RuntimeAddedPrefabs.Add(networkPrefab);
return true;
}
@@ -175,6 +185,7 @@ namespace Unity.Netcode
}
m_Prefabs.Remove(prefab);
+ m_RuntimeAddedPrefabs.Remove(prefab);
OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash);
NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash);
}
@@ -203,6 +214,15 @@ namespace Unity.Netcode
return;
}
}
+
+ for (int i = 0; i < m_RuntimeAddedPrefabs.Count; i++)
+ {
+ if (m_RuntimeAddedPrefabs[i].Prefab == prefab)
+ {
+ Remove(m_RuntimeAddedPrefabs[i]);
+ return;
+ }
+ }
}
///
diff --git a/Runtime/Connection/NetworkClient.cs b/Runtime/Connection/NetworkClient.cs
index bfc10d1..8a73478 100644
--- a/Runtime/Connection/NetworkClient.cs
+++ b/Runtime/Connection/NetworkClient.cs
@@ -7,30 +7,74 @@ namespace Unity.Netcode
///
public class NetworkClient
{
+ ///
+ /// Returns true if the session instance is considered a server
+ ///
+ internal bool IsServer { get; set; }
+
+ ///
+ /// Returns true if the session instance is considered a client
+ ///
+ internal bool IsClient { get; set; }
+
+ ///
+ /// Returns true if the session instance is considered a host
+ ///
+ internal bool IsHost => IsClient && IsServer;
+
+ ///
+ /// When true, the client is connected, approved, and synchronized with
+ /// the server.
+ ///
+ internal bool IsConnected { get; set; }
+
+ ///
+ /// Is true when the client has been approved.
+ ///
+ internal bool IsApproved { get; set; }
+
///
/// The ClientId of the NetworkClient
///
+ // TODO-2023-Q2: Determine if we want to make this property a public get and internal/private set
+ // There is no reason for a user to want to set this, but this will fail the package-validation-suite
public ulong ClientId;
///
/// The PlayerObject of the Client
///
+ // TODO-2023-Q2: Determine if we want to make this property a public get and internal/private set
+ // There is no reason for a user to want to set this, but this will fail the package-validation-suite
public NetworkObject PlayerObject;
///
- /// The NetworkObject's owned by this Client
+ /// The list of NetworkObject's owned by this client instance
///
- public List OwnedObjects
- {
- get
- {
- if (PlayerObject != null && PlayerObject.NetworkManager != null && PlayerObject.NetworkManager.IsListening)
- {
- return PlayerObject.NetworkManager.SpawnManager.GetClientOwnedObjects(ClientId);
- }
+ public List OwnedObjects => IsConnected ? SpawnManager.GetClientOwnedObjects(ClientId) : new List();
- return new List();
+ internal NetworkSpawnManager SpawnManager { get; private set; }
+
+ internal void SetRole(bool isServer, bool isClient, NetworkManager networkManager = null)
+ {
+ IsServer = isServer;
+ IsClient = isClient;
+ if (!IsServer && !isClient)
+ {
+ PlayerObject = null;
+ ClientId = 0;
+ IsConnected = false;
+ IsApproved = false;
}
+
+ if (networkManager != null)
+ {
+ SpawnManager = networkManager.SpawnManager;
+ }
+ }
+
+ internal void AssignPlayerObject(ref NetworkObject networkObject)
+ {
+ PlayerObject = networkObject;
}
}
}
diff --git a/Runtime/Connection/NetworkConnectionManager.cs b/Runtime/Connection/NetworkConnectionManager.cs
new file mode 100644
index 0000000..b59d99e
--- /dev/null
+++ b/Runtime/Connection/NetworkConnectionManager.cs
@@ -0,0 +1,1109 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Profiling;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace Unity.Netcode
+{
+ ///
+ /// The NGO connection manager handles:
+ /// - Client Connections
+ /// - Client Approval
+ /// - Processing s.
+ /// - Client Disconnection
+ ///
+ // TODO 2023-Q2: Discuss what kind of public API exposure we want for this
+ public sealed class NetworkConnectionManager
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ private static ProfilerMarker s_TransportPollMarker = new ProfilerMarker($"{nameof(NetworkManager)}.TransportPoll");
+ private static ProfilerMarker s_TransportConnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportConnect");
+ private static ProfilerMarker s_HandleIncomingData = new ProfilerMarker($"{nameof(NetworkManager)}.{nameof(NetworkMessageManager.HandleIncomingData)}");
+ private static ProfilerMarker s_TransportDisconnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportDisconnect");
+#endif
+
+ ///
+ /// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
+ /// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
+ ///
+ public string DisconnectReason { get; internal set; }
+
+ ///
+ /// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
+ ///
+ public event Action OnClientConnectedCallback = null;
+
+ ///
+ /// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects.
+ ///
+ public event Action OnClientDisconnectCallback = null;
+
+ internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId);
+
+ ///
+ /// The callback to invoke if the fails.
+ ///
+ ///
+ /// A failure of the transport is always followed by the shutting down. Recovering
+ /// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
+ /// recreating a new service allocation depending on the transport) and restarting the client/server/host.
+ ///
+ public event Action OnTransportFailure;
+
+ ///
+ /// Is true when a server or host is listening for connections.
+ /// Is true when a client is connecting or connected to a network session.
+ /// Is false when not listening, connecting, or connected.
+ ///
+ public bool IsListening { get; internal set; }
+
+ internal NetworkManager NetworkManager;
+ internal NetworkMessageManager MessageManager;
+
+ internal NetworkClient LocalClient = new NetworkClient();
+ internal Dictionary ClientsToApprove = new Dictionary();
+
+ internal Dictionary ConnectedClients = new Dictionary();
+ internal Dictionary ClientIdToTransportIdMap = new Dictionary();
+ internal Dictionary TransportIdToClientIdMap = new Dictionary();
+ internal List ConnectedClientsList = new List();
+ internal List ConnectedClientIds = new List();
+ internal Action ConnectionApprovalCallback;
+
+ ///
+ /// Use and to add or remove
+ /// Use to internally access the pending client dictionary
+ ///
+ private Dictionary m_PendingClients = new Dictionary();
+
+ internal IReadOnlyDictionary PendingClients => m_PendingClients;
+
+ internal Coroutine LocalClientApprovalCoroutine;
+
+ ///
+ /// Client-Side:
+ /// Starts the client-side approval timeout coroutine
+ ///
+ ///
+ internal void StartClientApprovalCoroutine(ulong clientId)
+ {
+ LocalClientApprovalCoroutine = NetworkManager.StartCoroutine(ApprovalTimeout(clientId));
+ }
+
+ ///
+ /// Client-Side:
+ /// Stops the client-side approval timeout when it is approved.
+ ///
+ ///
+ internal void StopClientApprovalCoroutine()
+ {
+ if (LocalClientApprovalCoroutine != null)
+ {
+ NetworkManager.StopCoroutine(LocalClientApprovalCoroutine);
+ LocalClientApprovalCoroutine = null;
+ }
+ }
+
+ ///
+ /// Server-Side:
+ /// Handles the issue with populating NetworkManager.PendingClients
+ ///
+ internal void AddPendingClient(ulong clientId)
+ {
+ m_PendingClients.Add(clientId, new PendingClient()
+ {
+ ClientId = clientId,
+ ConnectionState = PendingClient.State.PendingConnection,
+ ApprovalCoroutine = NetworkManager.StartCoroutine(ApprovalTimeout(clientId))
+ });
+
+ NetworkManager.PendingClients.Add(clientId, PendingClients[clientId]);
+ }
+
+ ///
+ /// Server-Side:
+ /// Handles the issue with depopulating NetworkManager.PendingClients
+ ///
+ internal void RemovePendingClient(ulong clientId)
+ {
+ if (m_PendingClients.ContainsKey(clientId) && m_PendingClients[clientId].ApprovalCoroutine != null)
+ {
+ NetworkManager.StopCoroutine(m_PendingClients[clientId].ApprovalCoroutine);
+ }
+ m_PendingClients.Remove(clientId);
+ NetworkManager.PendingClients.Remove(clientId);
+ }
+
+ ///
+ /// Used to generate client identifiers
+ ///
+ private ulong m_NextClientId = 1;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ulong TransportIdToClientId(ulong transportId)
+ {
+ if (transportId == GetServerTransportId())
+ {
+ return NetworkManager.ServerClientId;
+ }
+
+ if (TransportIdToClientIdMap.TryGetValue(transportId, out var clientId))
+ {
+ return clientId;
+ }
+
+ if (NetworkLog.CurrentLogLevel == LogLevel.Developer)
+ {
+ NetworkLog.LogWarning($"Trying to get the NGO client ID map for the transport ID ({transportId}) but did not find the map entry! Returning default transport ID value.");
+ }
+
+ return default;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ulong ClientIdToTransportId(ulong clientId)
+ {
+ if (clientId == NetworkManager.ServerClientId)
+ {
+ return GetServerTransportId();
+ }
+
+ if (ClientIdToTransportIdMap.TryGetValue(clientId, out var transportClientId))
+ {
+ return transportClientId;
+ }
+
+ if (NetworkLog.CurrentLogLevel == LogLevel.Developer)
+ {
+ NetworkLog.LogWarning($"Trying to get the transport client ID map for the NGO client ID ({clientId}) but did not find the map entry! Returning default transport ID value.");
+ }
+
+ return default;
+ }
+
+ ///
+ /// Gets the networkId of the server
+ ///
+ internal ulong ServerTransportId => GetServerTransportId();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ulong GetServerTransportId()
+ {
+ if (NetworkManager != null)
+ {
+ var transport = NetworkManager.NetworkConfig.NetworkTransport;
+ if (transport != null)
+ {
+ return transport.ServerClientId;
+ }
+
+ throw new NullReferenceException($"The transport in the active {nameof(NetworkConfig)} is null");
+ }
+
+ throw new Exception($"There is no {nameof(NetworkManager)} assigned to this instance!");
+ }
+
+ ///
+ /// Handles cleaning up the transport id/client id tables after receiving a disconnect event from transport
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ulong TransportIdCleanUp(ulong transportId)
+ {
+ // This check is for clients that attempted to connect but failed.
+ // When this happens, the client will not have an entry within the m_TransportIdToClientIdMap or m_ClientIdToTransportIdMap lookup tables so we exit early and just return 0 to be used for the disconnect event.
+ if (!LocalClient.IsServer && !TransportIdToClientIdMap.ContainsKey(transportId))
+ {
+ return 0;
+ }
+
+ var clientId = TransportIdToClientId(transportId);
+ TransportIdToClientIdMap.Remove(transportId);
+ ClientIdToTransportIdMap.Remove(clientId);
+ return clientId;
+ }
+
+ internal void PollAndHandleNetworkEvents()
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_TransportPollMarker.Begin();
+#endif
+ NetworkEvent networkEvent;
+ do
+ {
+ networkEvent = NetworkManager.NetworkConfig.NetworkTransport.PollEvent(out ulong transportClientId, out ArraySegment payload, out float receiveTime);
+ HandleNetworkEvent(networkEvent, transportClientId, payload, receiveTime);
+ // Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum)
+ } while (NetworkManager.IsListening && networkEvent != NetworkEvent.Nothing);
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_TransportPollMarker.End();
+#endif
+ }
+
+ ///
+ /// Event driven NetworkTransports (like UnityTransport) NetworkEvent handling
+ ///
+ ///
+ /// Polling NetworkTransports invoke this directly
+ ///
+ internal void HandleNetworkEvent(NetworkEvent networkEvent, ulong transportClientId, ArraySegment payload, float receiveTime)
+ {
+ switch (networkEvent)
+ {
+ case NetworkEvent.Connect:
+ ConnectEventHandler(transportClientId);
+ break;
+ case NetworkEvent.Data:
+ DataEventHandler(transportClientId, ref payload, receiveTime);
+ break;
+ case NetworkEvent.Disconnect:
+ DisconnectEventHandler(transportClientId);
+ break;
+ case NetworkEvent.TransportFailure:
+ TransportFailureEventHandler();
+ break;
+ }
+ }
+
+ ///
+ /// Handles a event.
+ ///
+ internal void ConnectEventHandler(ulong transportClientId)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_TransportConnect.Begin();
+#endif
+ // Assumptions:
+ // - When server receives a connection, it *must be* a client
+ // - When client receives one, it *must be* the server
+ // Client's can't connect to or talk to other clients.
+ // Server is a sentinel so only one exists, if we are server, we can't be connecting to it.
+ var clientId = transportClientId;
+ if (LocalClient.IsServer)
+ {
+ clientId = m_NextClientId++;
+ }
+ else
+ {
+ clientId = NetworkManager.ServerClientId;
+ }
+
+ ClientIdToTransportIdMap[clientId] = transportClientId;
+ TransportIdToClientIdMap[transportClientId] = clientId;
+ MessageManager.ClientConnected(clientId);
+
+ if (LocalClient.IsServer)
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo("Client Connected");
+ }
+
+ AddPendingClient(clientId);
+ }
+ else
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo("Connected");
+ }
+
+ SendConnectionRequest();
+ StartClientApprovalCoroutine(clientId);
+ }
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_TransportConnect.End();
+#endif
+ }
+
+ ///
+ /// Handles a event.
+ ///
+ internal void DataEventHandler(ulong transportClientId, ref ArraySegment payload, float receiveTime)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_HandleIncomingData.Begin();
+#endif
+ var clientId = TransportIdToClientId(transportClientId);
+ MessageManager.HandleIncomingData(clientId, payload, receiveTime);
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_HandleIncomingData.End();
+#endif
+ }
+
+ ///
+ /// Handles a event.
+ ///
+ internal void DisconnectEventHandler(ulong transportClientId)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_TransportDisconnect.Begin();
+#endif
+ var clientId = TransportIdCleanUp(transportClientId);
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo($"Disconnect Event From {clientId}");
+ }
+
+ // Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
+ MessageManager.ProcessIncomingMessageQueue();
+
+ try
+ {
+ OnClientDisconnectCallback?.Invoke(clientId);
+ }
+ catch (Exception exception)
+ {
+ Debug.LogException(exception);
+ }
+
+ if (LocalClient.IsServer)
+ {
+ OnClientDisconnectFromServer(clientId);
+ }
+ else
+ {
+ // We must pass true here and not process any sends messages as we are no longer connected and thus there is no one to send any messages to and this will cause an exception within UnityTransport as the client ID is no longer valid.
+ NetworkManager.Shutdown(true);
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ s_TransportDisconnect.End();
+#endif
+ }
+
+ ///
+ /// Handles a event.
+ ///
+ internal void TransportFailureEventHandler(bool duringStart = false)
+ {
+ var clientSeverOrHost = LocalClient.IsServer ? LocalClient.IsHost ? "Host" : "Server" : "Client";
+ var whenFailed = duringStart ? "start failure" : "failure";
+ NetworkLog.LogError($"{clientSeverOrHost} is shutting down due to network transport {whenFailed} of {NetworkManager.NetworkConfig.NetworkTransport.GetType().Name}!");
+ OnTransportFailure?.Invoke();
+
+ // If we had a transport failure when trying to start, reset the local client roles and directly invoke the internal shutdown.
+ if (duringStart)
+ {
+ LocalClient.SetRole(false, false);
+ NetworkManager.ShutdownInternal();
+ }
+ else
+ {
+ // Otherwise, stop processing messages and shutdown the normal way
+ NetworkManager.Shutdown(true);
+ }
+ }
+
+ ///
+ /// Client-Side:
+ /// Upon transport connecting, the client will send a connection request
+ ///
+ private void SendConnectionRequest()
+ {
+ var message = new ConnectionRequestMessage
+ {
+ // Since only a remote client will send a connection request, we should always force the rebuilding of the NetworkConfig hash value
+ ConfigHash = NetworkManager.NetworkConfig.GetConfig(false),
+ ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval,
+ ConnectionData = NetworkManager.NetworkConfig.ConnectionData,
+ MessageVersions = new NativeArray(MessageManager.MessageHandlers.Length, Allocator.Temp)
+ };
+
+ for (int index = 0; index < MessageManager.MessageHandlers.Length; index++)
+ {
+ if (MessageManager.MessageTypes[index] != null)
+ {
+ var type = MessageManager.MessageTypes[index];
+ message.MessageVersions[index] = new MessageVersionData
+ {
+ Hash = XXHash.Hash32(type.FullName),
+ Version = MessageManager.GetLocalVersion(type)
+ };
+ }
+ }
+
+ SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ServerClientId);
+ message.MessageVersions.Dispose();
+ }
+
+ ///
+ /// Approval time out coroutine
+ ///
+ private IEnumerator ApprovalTimeout(ulong clientId)
+ {
+ var timeStarted = LocalClient.IsServer ? NetworkManager.LocalTime.TimeAsFloat : NetworkManager.RealTimeProvider.RealTimeSinceStartup;
+ var timedOut = false;
+ var connectionApproved = false;
+ var connectionNotApproved = false;
+ var timeoutMarker = timeStarted + NetworkManager.NetworkConfig.ClientConnectionBufferTimeout;
+
+ while (NetworkManager.IsListening && !NetworkManager.ShutdownInProgress && !timedOut && !connectionApproved)
+ {
+ yield return null;
+ // Check if we timed out
+ timedOut = timeoutMarker < (LocalClient.IsServer ? NetworkManager.LocalTime.TimeAsFloat : NetworkManager.RealTimeProvider.RealTimeSinceStartup);
+
+ if (LocalClient.IsServer)
+ {
+ // When the client is no longer in the pending clients list and is in the connected clients list it has been approved
+ connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId);
+
+ // For the server side, if the client is in neither list then it was declined or the client disconnected
+ connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId);
+ }
+ else
+ {
+ connectionApproved = NetworkManager.LocalClient.IsApproved;
+ }
+ }
+
+ // Exit coroutine if we are no longer listening or a shutdown is in progress (client or server)
+ if (!NetworkManager.IsListening || NetworkManager.ShutdownInProgress)
+ {
+ yield break;
+ }
+
+ // If the client timed out or was not approved
+ if (timedOut || connectionNotApproved)
+ {
+ // Timeout
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ if (timedOut)
+ {
+ if (LocalClient.IsServer)
+ {
+ // Log a warning that the transport detected a connection but then did not receive a follow up connection request message.
+ // (hacking or something happened to the server's network connection)
+ NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message.");
+ }
+ else
+ {
+ // We only provide informational logging for the client side
+ NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request.");
+ }
+ }
+ else if (connectionNotApproved)
+ {
+ NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved.");
+ }
+ }
+
+ if (LocalClient.IsServer)
+ {
+ DisconnectClient(clientId);
+ }
+ else
+ {
+ NetworkManager.Shutdown(true);
+ }
+ }
+ }
+
+ ///
+ /// Server-Side:
+ /// Handles approval while processing a client connection request
+ ///
+ internal void ApproveConnection(ref ConnectionRequestMessage connectionRequestMessage, ref NetworkContext context)
+ {
+ // Note: Delegate creation allocates.
+ // Note: ToArray() also allocates. :(
+ var response = new NetworkManager.ConnectionApprovalResponse();
+ ClientsToApprove[context.SenderId] = response;
+
+ ConnectionApprovalCallback(
+ new NetworkManager.ConnectionApprovalRequest
+ {
+ Payload = connectionRequestMessage.ConnectionData,
+ ClientNetworkId = context.SenderId
+ }, response);
+ }
+
+ ///
+ /// Server-Side:
+ /// Processes pending approvals and removes any stale pending clients
+ ///
+ internal void ProcessPendingApprovals()
+ {
+ List senders = null;
+
+ foreach (var responsePair in ClientsToApprove)
+ {
+ var response = responsePair.Value;
+ var senderId = responsePair.Key;
+
+ if (!response.Pending)
+ {
+ try
+ {
+ HandleConnectionApproval(senderId, response);
+
+ senders ??= new List();
+ senders.Add(senderId);
+ }
+ catch (Exception e)
+ {
+ Debug.LogException(e);
+ }
+ }
+ }
+
+ if (senders != null)
+ {
+ foreach (var sender in senders)
+ {
+ ClientsToApprove.Remove(sender);
+ }
+ }
+ }
+
+ ///
+ /// Server Side: Handles the approval of a client
+ ///
+ ///
+ /// This will spawn the player prefab as well as start client synchronization if is enabled
+ ///
+ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.ConnectionApprovalResponse response)
+ {
+ LocalClient.IsApproved = response.Approved;
+ if (response.Approved)
+ {
+ // The client was approved, stop the server-side approval time out coroutine
+ RemovePendingClient(ownerClientId);
+
+ var client = AddClient(ownerClientId);
+
+ if (response.CreatePlayerObject)
+ {
+ var prefabNetworkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent();
+ var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash;
+
+ // Generate a SceneObject for the player object to spawn
+ // Note: This is only to create the local NetworkObject, many of the serialized properties of the player prefab will be set when instantiated.
+ var sceneObject = new NetworkObject.SceneObject
+ {
+ OwnerClientId = ownerClientId,
+ IsPlayerObject = true,
+ IsSceneObject = false,
+ HasTransform = prefabNetworkObject.SynchronizeTransform,
+ Hash = playerPrefabHash,
+ TargetClientId = ownerClientId,
+ Transform = new NetworkObject.SceneObject.TransformData
+ {
+ Position = response.Position.GetValueOrDefault(),
+ Rotation = response.Rotation.GetValueOrDefault()
+ }
+ };
+
+ // Create the player NetworkObject locally
+ var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
+
+ // Spawn the player NetworkObject locally
+ NetworkManager.SpawnManager.SpawnNetworkObjectLocally(
+ networkObject,
+ NetworkManager.SpawnManager.GetNetworkObjectId(),
+ sceneObject: false,
+ playerObject: true,
+ ownerClientId,
+ destroyWithScene: false);
+
+ client.AssignPlayerObject(ref networkObject);
+ }
+
+ // Server doesn't send itself the connection approved message
+ if (ownerClientId != NetworkManager.ServerClientId)
+ {
+ var message = new ConnectionApprovedMessage
+ {
+ OwnerClientId = ownerClientId,
+ NetworkTick = NetworkManager.LocalTime.Tick
+ };
+ if (!NetworkManager.NetworkConfig.EnableSceneManagement)
+ {
+ if (NetworkManager.SpawnManager.SpawnedObjectsList.Count != 0)
+ {
+ message.SpawnedObjectsList = NetworkManager.SpawnManager.SpawnedObjectsList;
+ }
+ }
+
+ message.MessageVersions = new NativeArray(MessageManager.MessageHandlers.Length, Allocator.Temp);
+ for (int index = 0; index < MessageManager.MessageHandlers.Length; index++)
+ {
+ if (MessageManager.MessageTypes[index] != null)
+ {
+ var type = MessageManager.MessageTypes[index];
+ message.MessageVersions[index] = new MessageVersionData
+ {
+ Hash = XXHash.Hash32(type.FullName),
+ Version = MessageManager.GetLocalVersion(type)
+ };
+ }
+ }
+
+ SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
+ message.MessageVersions.Dispose();
+
+ // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
+ if (!NetworkManager.NetworkConfig.EnableSceneManagement)
+ {
+ InvokeOnClientConnectedCallback(ownerClientId);
+ }
+ else
+ {
+ NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId);
+ }
+ }
+ else // Server just adds itself as an observer to all spawned NetworkObjects
+ {
+ LocalClient = client;
+ NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
+ }
+
+ if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkManager.NetworkConfig.PlayerPrefab == null))
+ {
+ return;
+ }
+
+ // Separating this into a contained function call for potential further future separation of when this notification is sent.
+ ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash);
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(response.Reason))
+ {
+ var disconnectReason = new DisconnectReasonMessage
+ {
+ Reason = response.Reason
+ };
+ SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
+ MessageManager.ProcessSendQueues();
+ }
+
+ DisconnectRemoteClient(ownerClientId);
+ }
+ }
+
+ ///
+ /// Spawns the newly approved player
+ ///
+ /// new player client identifier
+ /// the prefab GlobalObjectIdHash value for this player
+ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash)
+ {
+ foreach (var clientPair in ConnectedClients)
+ {
+ if (clientPair.Key == clientId ||
+ clientPair.Key == NetworkManager.ServerClientId || // Server already spawned it
+ ConnectedClients[clientId].PlayerObject == null ||
+ !ConnectedClients[clientId].PlayerObject.Observers.Contains(clientPair.Key))
+ {
+ continue; //The new client.
+ }
+
+ var message = new CreateObjectMessage
+ {
+ ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key)
+ };
+ message.ObjectInfo.Hash = playerPrefabHash;
+ message.ObjectInfo.IsSceneObject = false;
+ message.ObjectInfo.HasParent = false;
+ message.ObjectInfo.IsPlayerObject = true;
+ message.ObjectInfo.OwnerClientId = clientId;
+ var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
+ NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
+ }
+ }
+
+ ///
+ /// Server-Side:
+ /// Creates a new and handles updating the associated
+ /// connected clients lists.
+ ///
+ internal NetworkClient AddClient(ulong clientId)
+ {
+ var networkClient = LocalClient;
+ if (clientId != NetworkManager.ServerClientId)
+ {
+ networkClient = new NetworkClient();
+ networkClient.SetRole(isServer: false, isClient: true, NetworkManager);
+ networkClient.ClientId = clientId;
+ }
+
+ ConnectedClients.Add(clientId, networkClient);
+ ConnectedClientsList.Add(networkClient);
+ ConnectedClientIds.Add(clientId);
+ return networkClient;
+ }
+
+ ///
+ /// Server-Side:
+ /// Invoked when a client is disconnected from a server-host
+ ///
+ internal void OnClientDisconnectFromServer(ulong clientId)
+ {
+ if (!LocalClient.IsServer)
+ {
+ throw new Exception("[OnClientDisconnectFromServer] Was invoked by non-server instance!");
+ }
+
+ // If we are shutting down and this is the server or host disconnecting, then ignore
+ // clean up as everything that needs to be destroyed will be during shutdown.
+ if (NetworkManager.ShutdownInProgress && clientId == NetworkManager.ServerClientId)
+ {
+ return;
+ }
+
+ if (ConnectedClients.TryGetValue(clientId, out NetworkClient networkClient))
+ {
+ var playerObject = networkClient.PlayerObject;
+ if (playerObject != null)
+ {
+ if (!playerObject.DontDestroyWithOwner)
+ {
+ if (NetworkManager.PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
+ {
+ NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
+ }
+ else if (playerObject.IsSpawned)
+ {
+ // Call despawn to assure NetworkBehaviour.OnNetworkDespawn is invoked on the server-side (when the client side disconnected).
+ // This prevents the issue (when just destroying the GameObject) where any NetworkBehaviour component(s) destroyed before the NetworkObject would not have OnNetworkDespawn invoked.
+ NetworkManager.SpawnManager.DespawnObject(playerObject, true);
+ }
+ }
+ else
+ {
+ playerObject.RemoveOwnership();
+ }
+ }
+
+ // Get the NetworkObjects owned by the disconnected client
+ var clientOwnedObjects = NetworkManager.SpawnManager.GetClientOwnedObjects(clientId);
+ if (clientOwnedObjects == null)
+ {
+ // This could happen if a client is never assigned a player object and is disconnected
+ // Only log this in verbose/developer mode
+ if (NetworkManager.LogLevel == LogLevel.Developer)
+ {
+ NetworkLog.LogWarning($"ClientID {clientId} disconnected with (0) zero owned objects! Was a player prefab not assigned?");
+ }
+ }
+ else
+ {
+ // Handle changing ownership and prefab handlers
+ // TODO-2023: Look into whether in-scene placed NetworkObjects could be destroyed if ownership changes to a client
+ for (int i = clientOwnedObjects.Count - 1; i >= 0; i--)
+ {
+ var ownedObject = clientOwnedObjects[i];
+ if (ownedObject != null)
+ {
+ if (!ownedObject.DontDestroyWithOwner)
+ {
+ if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash))
+ {
+ NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]);
+ }
+ else
+ {
+ Object.Destroy(ownedObject.gameObject);
+ }
+ }
+ else
+ {
+ ownedObject.RemoveOwnership();
+ }
+ }
+ }
+ }
+
+ // TODO: Could(should?) be replaced with more memory per client, by storing the visibility
+ foreach (var sobj in NetworkManager.SpawnManager.SpawnedObjectsList)
+ {
+ sobj.Observers.Remove(clientId);
+ }
+
+ if (ConnectedClients.ContainsKey(clientId))
+ {
+ ConnectedClientsList.Remove(ConnectedClients[clientId]);
+ ConnectedClients.Remove(clientId);
+ }
+
+ ConnectedClientIds.Remove(clientId);
+ }
+
+ // If the client ID transport map exists
+ if (ClientIdToTransportIdMap.ContainsKey(clientId))
+ {
+ var transportId = ClientIdToTransportId(clientId);
+ NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
+
+ try
+ {
+ OnClientDisconnectCallback?.Invoke(clientId);
+ }
+ catch (Exception exception)
+ {
+ Debug.LogException(exception);
+ }
+
+ // Clean up the transport to client (and vice versa) mappings
+ TransportIdCleanUp(transportId);
+ }
+
+ // Assure the client id is no longer in the pending clients list
+ // and stop the server-side client approval timeout since the client
+ // is no longer connected.
+ RemovePendingClient(clientId);
+
+ // Handle cleaning up the server-side client send queue
+ MessageManager.ClientDisconnected(clientId);
+ }
+
+ ///
+ /// Server-Side:
+ /// Invoked when disconnecting a remote client
+ ///
+ internal void DisconnectRemoteClient(ulong clientId)
+ {
+ MessageManager.ProcessSendQueues();
+ OnClientDisconnectFromServer(clientId);
+ }
+
+ ///
+ /// Server-Side:
+ /// Invoked when disconnecting a remote client with the option to provide
+ /// a reason.
+ ///
+ internal void DisconnectClient(ulong clientId, string reason = null)
+ {
+ if (!LocalClient.IsServer)
+ {
+ throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
+ }
+
+ if (!string.IsNullOrEmpty(reason))
+ {
+ var disconnectReason = new DisconnectReasonMessage
+ {
+ Reason = reason
+ };
+ SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
+ }
+
+ DisconnectRemoteClient(clientId);
+ }
+
+ ///
+ /// Should be invoked when starting a server-host or client
+ ///
+ ///
+ internal void Initialize(NetworkManager networkManager)
+ {
+ // Prepare for a new session
+ LocalClient.IsApproved = false;
+ m_PendingClients.Clear();
+ ConnectedClients.Clear();
+ ConnectedClientsList.Clear();
+ ConnectedClientIds.Clear();
+ ClientIdToTransportIdMap.Clear();
+ TransportIdToClientIdMap.Clear();
+ ClientsToApprove.Clear();
+ NetworkObject.OrphanChildren.Clear();
+ DisconnectReason = string.Empty;
+
+ NetworkManager = networkManager;
+ MessageManager = networkManager.MessageManager;
+
+ NetworkManager.NetworkConfig.NetworkTransport.NetworkMetrics = NetworkManager.MetricsManager.NetworkMetrics;
+
+ NetworkManager.NetworkConfig.NetworkTransport.OnTransportEvent += HandleNetworkEvent;
+ NetworkManager.NetworkConfig.NetworkTransport.Initialize(networkManager);
+ }
+
+ ///
+ /// Should be called when shutting down the NetworkManager
+ ///
+ internal void Shutdown()
+ {
+ LocalClient.IsApproved = false;
+ LocalClient.IsConnected = false;
+ if (LocalClient.IsServer)
+ {
+ // make sure all messages are flushed before transport disconnect clients
+ MessageManager?.ProcessSendQueues();
+
+ // Build a list of all client ids to be disconnected
+ var disconnectedIds = new HashSet();
+
+ //Don't know if I have to disconnect the clients. I'm assuming the NetworkTransport does all the cleaning on shutdown. But this way the clients get a disconnect message from server (so long it does't get lost)
+ var serverTransportId = NetworkManager.NetworkConfig.NetworkTransport.ServerClientId;
+ foreach (KeyValuePair pair in ConnectedClients)
+ {
+ if (!disconnectedIds.Contains(pair.Key))
+ {
+ if (pair.Key == serverTransportId)
+ {
+ continue;
+ }
+
+ disconnectedIds.Add(pair.Key);
+ }
+ }
+
+ foreach (KeyValuePair pair in PendingClients)
+ {
+ if (!disconnectedIds.Contains(pair.Key))
+ {
+ if (pair.Key == serverTransportId)
+ {
+ continue;
+ }
+
+ disconnectedIds.Add(pair.Key);
+ }
+ }
+
+ foreach (var clientId in disconnectedIds)
+ {
+ DisconnectRemoteClient(clientId);
+ }
+ }
+ else if (NetworkManager != null && NetworkManager.IsListening && LocalClient.IsClient)
+ {
+ // Client only, send disconnect and if transport throws and exception, log the exception and continue the shutdown sequence (or forever be shutting down)
+ try
+ {
+ NetworkManager.NetworkConfig.NetworkTransport.DisconnectLocalClient();
+ }
+ catch (Exception ex)
+ {
+ Debug.LogException(ex);
+ }
+ }
+
+ if (NetworkManager != null && NetworkManager.NetworkConfig?.NetworkTransport != null)
+ {
+ NetworkManager.NetworkConfig.NetworkTransport.OnTransportEvent -= HandleNetworkEvent;
+ }
+
+ // This is required for handling the potential scenario where multiple NetworkManager instances are created.
+ // See MTT-860 for more information
+ if (IsListening)
+ {
+ //The Transport is set during initialization, thus it is possible for the Transport to be null
+ var transport = NetworkManager.NetworkConfig?.NetworkTransport;
+ if (transport != null)
+ {
+ transport.Shutdown();
+
+ if (NetworkManager.LogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo($"{nameof(NetworkConnectionManager)}.{nameof(Shutdown)}() -> {nameof(IsListening)} && {nameof(NetworkManager.NetworkConfig.NetworkTransport)} != null -> {nameof(NetworkTransport)}.{nameof(NetworkTransport.Shutdown)}()");
+ }
+ }
+ }
+ }
+
+ internal unsafe int SendMessage(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
+ where TMessageType : INetworkMessage
+ where TClientIdListType : IReadOnlyList
+ {
+ // Prevent server sending to itself
+ if (LocalClient.IsServer)
+ {
+ ulong* nonServerIds = stackalloc ulong[clientIds.Count];
+ int newIdx = 0;
+ for (int idx = 0; idx < clientIds.Count; ++idx)
+ {
+ if (clientIds[idx] == NetworkManager.ServerClientId)
+ {
+ continue;
+ }
+
+ nonServerIds[newIdx++] = clientIds[idx];
+ }
+
+ if (newIdx == 0)
+ {
+ return 0;
+ }
+
+ return MessageManager.SendMessage(ref message, delivery, nonServerIds, newIdx);
+ }
+
+ // else
+ if (clientIds.Count != 1 || clientIds[0] != NetworkManager.ServerClientId)
+ {
+ throw new ArgumentException($"Clients may only send messages to {nameof(NetworkManager.ServerClientId)}");
+ }
+
+ return MessageManager.SendMessage(ref message, delivery, clientIds);
+ }
+
+ internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, ulong* clientIds, int numClientIds)
+ where T : INetworkMessage
+ {
+ // Prevent server sending to itself
+ if (LocalClient.IsServer)
+ {
+ ulong* nonServerIds = stackalloc ulong[numClientIds];
+ int newIdx = 0;
+ for (int idx = 0; idx < numClientIds; ++idx)
+ {
+ if (clientIds[idx] == NetworkManager.ServerClientId)
+ {
+ continue;
+ }
+
+ nonServerIds[newIdx++] = clientIds[idx];
+ }
+
+ if (newIdx == 0)
+ {
+ return 0;
+ }
+
+ return MessageManager.SendMessage(ref message, delivery, nonServerIds, newIdx);
+ }
+
+ // else
+ if (numClientIds != 1 || clientIds[0] != NetworkManager.ServerClientId)
+ {
+ throw new ArgumentException($"Clients may only send messages to {nameof(NetworkManager.ServerClientId)}");
+ }
+
+ return MessageManager.SendMessage(ref message, delivery, clientIds, numClientIds);
+ }
+
+ internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in NativeArray clientIds)
+ where T : INetworkMessage
+ {
+ return SendMessage(ref message, delivery, (ulong*)clientIds.GetUnsafePtr(), clientIds.Length);
+ }
+
+ internal int SendMessage(ref T message, NetworkDelivery delivery, ulong clientId)
+ where T : INetworkMessage
+ {
+ // Prevent server sending to itself
+ if (LocalClient.IsServer && clientId == NetworkManager.ServerClientId)
+ {
+ return 0;
+ }
+
+ if (!LocalClient.IsServer && clientId != NetworkManager.ServerClientId)
+ {
+ throw new ArgumentException($"Clients may only send messages to {nameof(NetworkManager.ServerClientId)}");
+ }
+
+ return MessageManager.SendMessage(ref message, delivery, clientId);
+ }
+ }
+}
diff --git a/Runtime/Connection/NetworkConnectionManager.cs.meta b/Runtime/Connection/NetworkConnectionManager.cs.meta
new file mode 100644
index 0000000..45a24a4
--- /dev/null
+++ b/Runtime/Connection/NetworkConnectionManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d98eff74d73bc2a42bd5624c47ce8fe1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Connection/PendingClient.cs b/Runtime/Connection/PendingClient.cs
index 5bbf392..e381dbb 100644
--- a/Runtime/Connection/PendingClient.cs
+++ b/Runtime/Connection/PendingClient.cs
@@ -1,10 +1,15 @@
+using UnityEngine;
+
namespace Unity.Netcode
{
///
+ /// Server-Side Only:
/// A class representing a client that is currently in the process of connecting
///
public class PendingClient
{
+ internal Coroutine ApprovalCoroutine = null;
+
///
/// The ClientId of the client
///
diff --git a/Runtime/Core/ComponentFactory.cs b/Runtime/Core/ComponentFactory.cs
index 6bb8172..52945c4 100644
--- a/Runtime/Core/ComponentFactory.cs
+++ b/Runtime/Core/ComponentFactory.cs
@@ -51,7 +51,7 @@ namespace Unity.Netcode
///
public static void SetDefaults()
{
- SetDefault(networkManager => new DeferredMessageManager(networkManager));
+ SetDefault(networkManager => new DeferredMessageManager(networkManager));
SetDefault(networkManager => new RealTimeProvider());
}
diff --git a/Runtime/Core/NetworkBehaviour.cs b/Runtime/Core/NetworkBehaviour.cs
index 6efb88c..782ecba 100644
--- a/Runtime/Core/NetworkBehaviour.cs
+++ b/Runtime/Core/NetworkBehaviour.cs
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
using Unity.Collections;
using UnityEngine;
@@ -65,7 +63,7 @@ namespace Unity.Netcode
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
break;
case RpcDelivery.Unreliable:
- if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
+ if (bufferWriter.Length > NetworkManager.MessageManager.NonFragmentedMessageMaxSize)
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
@@ -86,7 +84,7 @@ namespace Unity.Netcode
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty.
- Header = new MessageHeader(),
+ Header = new NetworkMessageHeader(),
SerializedHeaderSize = 0,
MessageSize = 0
};
@@ -96,7 +94,7 @@ namespace Unity.Netcode
}
else
{
- rpcWriteSize = NetworkManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId);
+ rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId);
}
bufferWriter.Dispose();
@@ -146,7 +144,7 @@ namespace Unity.Netcode
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
break;
case RpcDelivery.Unreliable:
- if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
+ if (bufferWriter.Length > NetworkManager.MessageManager.NonFragmentedMessageMaxSize)
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
@@ -176,7 +174,7 @@ namespace Unity.Netcode
}
}
- rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
+ rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
}
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
{
@@ -195,7 +193,7 @@ namespace Unity.Netcode
}
}
- rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
+ rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
}
else
{
@@ -208,7 +206,7 @@ namespace Unity.Netcode
shouldSendToHost = true;
continue;
}
- rpcWriteSize = NetworkManager.MessagingSystem.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current);
+ rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current);
}
}
@@ -223,7 +221,7 @@ namespace Unity.Netcode
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty.
- Header = new MessageHeader(),
+ Header = new NetworkMessageHeader(),
SerializedHeaderSize = 0,
MessageSize = 0
};
@@ -277,6 +275,14 @@ namespace Unity.Netcode
#endif
}
+#pragma warning disable IDE1006 // disable naming rule violation check
+ // RuntimeAccessModifiersILPP will make this `protected`
+ internal static NativeList __createNativeList() where T : unmanaged
+#pragma warning restore IDE1006 // restore naming rule violation check
+ {
+ return new NativeList(Allocator.Temp);
+ }
+
internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ulong targetClientId)
{
var containerNameHoldingId = clientRpcParams.Send.TargetClientIds != null ? nameof(ClientRpcParams.Send.TargetClientIds) : nameof(ClientRpcParams.Send.TargetClientIdsNativeArray);
@@ -551,35 +557,25 @@ namespace Unity.Netcode
private readonly List> m_DeliveryMappedNetworkVariableIndices = new List>();
private readonly List m_DeliveryTypesForNetworkVariableGroups = new List();
+
+ // RuntimeAccessModifiersILPP will make this `protected`
internal readonly List NetworkVariableFields = new List();
- private static Dictionary s_FieldTypes = new Dictionary();
-
- private static FieldInfo[] GetFieldInfoForType(Type type)
+#pragma warning disable IDE1006 // disable naming rule violation check
+ // RuntimeAccessModifiersILPP will make this `protected`
+ internal virtual void __initializeVariables()
+#pragma warning restore IDE1006 // restore naming rule violation check
{
- if (!s_FieldTypes.ContainsKey(type))
- {
- s_FieldTypes.Add(type, GetFieldInfoForTypeRecursive(type));
- }
-
- return s_FieldTypes[type];
+ // ILPP generates code for all NetworkBehaviour subtypes to initialize each type's network variables.
}
- private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List list = null)
+#pragma warning disable IDE1006 // disable naming rule violation check
+ // RuntimeAccessModifiersILPP will make this `protected`
+ // Using this method here because ILPP doesn't seem to let us do visibility modification on properties.
+ internal void __nameNetworkVariable(NetworkVariableBase variable, string varName)
+#pragma warning restore IDE1006 // restore naming rule violation check
{
- if (list == null)
- {
- list = new List();
- }
-
- list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
-
- if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
- {
- return GetFieldInfoForTypeRecursive(type.BaseType, list);
- }
-
- return list.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
+ variable.Name = varName;
}
internal void InitializeVariables()
@@ -591,22 +587,7 @@ namespace Unity.Netcode
m_VarInit = true;
- var sortedFields = GetFieldInfoForType(GetType());
- for (int i = 0; i < sortedFields.Length; i++)
- {
- var fieldType = sortedFields[i].FieldType;
- if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
- {
- var instance = (NetworkVariableBase)sortedFields[i].GetValue(this) ?? throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
- instance.Initialize(this);
-
- var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
- var sanitizedVariableName = sortedFields[i].Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty);
- instanceNameProperty?.SetValue(instance, sanitizedVariableName);
-
- NetworkVariableFields.Add(instance);
- }
- }
+ __initializeVariables();
{
// Create index map for delivery types
@@ -718,7 +699,7 @@ namespace Unity.Netcode
// so we don't have to do this serialization work if we're not going to use the result.
if (IsServer && targetClientId == NetworkManager.ServerClientId)
{
- var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
+ var tmpWriter = new FastBufferWriter(NetworkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, NetworkManager.MessageManager.FragmentedMessageMaxSize);
using (tmpWriter)
{
message.Serialize(tmpWriter, message.Version);
@@ -726,7 +707,7 @@ namespace Unity.Netcode
}
else
{
- NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
+ NetworkManager.ConnectionManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
}
}
}
diff --git a/Runtime/Core/NetworkBehaviourUpdater.cs b/Runtime/Core/NetworkBehaviourUpdater.cs
index 2ebb281..0afe462 100644
--- a/Runtime/Core/NetworkBehaviourUpdater.cs
+++ b/Runtime/Core/NetworkBehaviourUpdater.cs
@@ -8,6 +8,8 @@ namespace Unity.Netcode
///
public class NetworkBehaviourUpdater
{
+ private NetworkManager m_NetworkManager;
+ private NetworkConnectionManager m_ConnectionManager;
private HashSet m_DirtyNetworkObjects = new HashSet();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -19,7 +21,7 @@ namespace Unity.Netcode
m_DirtyNetworkObjects.Add(networkObject);
}
- internal void NetworkBehaviourUpdate(NetworkManager networkManager)
+ internal void NetworkBehaviourUpdate()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_NetworkBehaviourUpdate.Begin();
@@ -30,7 +32,7 @@ namespace Unity.Netcode
// trying to process them, even if they were previously marked as dirty.
m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null);
- if (networkManager.IsServer)
+ if (m_ConnectionManager.LocalClient.IsServer)
{
foreach (var dirtyObj in m_DirtyNetworkObjects)
{
@@ -39,9 +41,9 @@ namespace Unity.Netcode
dirtyObj.ChildNetworkBehaviours[k].PreVariableUpdate();
}
- for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
+ for (int i = 0; i < m_ConnectionManager.ConnectedClientsList.Count; i++)
{
- var client = networkManager.ConnectedClientsList[i];
+ var client = m_ConnectionManager.ConnectedClientsList[i];
if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
{
@@ -104,5 +106,26 @@ namespace Unity.Netcode
}
}
+ internal void Initialize(NetworkManager networkManager)
+ {
+ m_NetworkManager = networkManager;
+ m_ConnectionManager = networkManager.ConnectionManager;
+ m_NetworkManager.NetworkTickSystem.Tick += NetworkBehaviourUpdater_Tick;
+ }
+
+ internal void Shutdown()
+ {
+ m_NetworkManager.NetworkTickSystem.Tick -= NetworkBehaviourUpdater_Tick;
+ }
+
+ // TODO 2023-Q2: Order of operations requires NetworkVariable updates first then showing NetworkObjects
+ private void NetworkBehaviourUpdater_Tick()
+ {
+ // First update NetworkVariables
+ NetworkBehaviourUpdate();
+
+ // Then show any NetworkObjects queued to be made visible/shown
+ m_NetworkManager.SpawnManager.HandleNetworkObjectShow();
+ }
}
}
diff --git a/Runtime/Core/NetworkManager.cs b/Runtime/Core/NetworkManager.cs
index 2ed0928..b08cb74 100644
--- a/Runtime/Core/NetworkManager.cs
+++ b/Runtime/Core/NetworkManager.cs
@@ -1,18 +1,10 @@
using System;
-using System.Collections;
using System.Collections.Generic;
-using Unity.Collections;
-using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
-#if MULTIPLAYER_TOOLS
-using Unity.Multiplayer.Tools;
-#endif
-using Unity.Profiling;
using UnityEngine.SceneManagement;
-using System.Runtime.CompilerServices;
using Debug = UnityEngine.Debug;
namespace Unity.Netcode
@@ -38,231 +30,274 @@ namespace Unity.Netcode
#pragma warning restore IDE1006 // restore naming rule violation check
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- private static ProfilerMarker s_SyncTime = new ProfilerMarker($"{nameof(NetworkManager)}.SyncTime");
- private static ProfilerMarker s_TransportPoll = new ProfilerMarker($"{nameof(NetworkManager)}.TransportPoll");
- private static ProfilerMarker s_TransportConnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportConnect");
- private static ProfilerMarker s_HandleIncomingData = new ProfilerMarker($"{nameof(NetworkManager)}.{nameof(HandleIncomingData)}");
- private static ProfilerMarker s_TransportDisconnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportDisconnect");
-#endif
-
- private const double k_TimeSyncFrequency = 1.0d; // sync every second
- private const float k_DefaultBufferSizeSec = 0.05f; // todo talk with UX/Product, find good default value for this
-
- internal static string PrefabDebugHelper(NetworkPrefab networkPrefab)
+ public void NetworkUpdate(NetworkUpdateStage updateStage)
{
- return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.name}\"";
- }
-
- internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
-
- internal void MarkNetworkObjectDirty(NetworkObject networkObject)
- {
- BehaviourUpdater.AddForUpdate(networkObject);
- }
-
- internal MessagingSystem MessagingSystem { get; private set; }
-
- private NetworkPrefabHandler m_PrefabHandler;
-
- internal Dictionary ClientsToApprove = new Dictionary();
-
- // Stores the objects that need to be shown at end-of-frame
- internal Dictionary> ObjectsToShowToClient = new Dictionary>();
-
- ///
- /// The instance created after starting the
- ///
- public NetworkPrefabHandler PrefabHandler
- {
- get
+ switch (updateStage)
{
- if (m_PrefabHandler == null)
- {
- m_PrefabHandler = new NetworkPrefabHandler();
- }
+ case NetworkUpdateStage.EarlyUpdate:
+ {
+ ConnectionManager.ProcessPendingApprovals();
+ ConnectionManager.PollAndHandleNetworkEvents();
- return m_PrefabHandler;
+ MessageManager.ProcessIncomingMessageQueue();
+ MessageManager.CleanupDisconnectedClients();
+ }
+ break;
+ case NetworkUpdateStage.PreUpdate:
+ {
+ NetworkTimeSystem.UpdateTime();
+ }
+ break;
+ case NetworkUpdateStage.PostLateUpdate:
+ {
+ // This should be invoked just prior to the MessageManager processes its outbound queue.
+ SceneManager.CheckForAndSendNetworkObjectSceneChanged();
+
+ // Process outbound messages
+ MessageManager.ProcessSendQueues();
+
+ // Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed.
+ MetricsManager.UpdateMetrics();
+
+ // TODO 2023-Q2: Determine a better way to handle this
+ NetworkObject.VerifyParentingStatus();
+
+ // This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period.
+ DeferredMessageManager.CleanupStaleTriggers();
+
+ // TODO 2023-Q2: Determine a better way to handle this
+ if (m_ShuttingDown)
+ {
+ ShutdownInternal();
+ }
+ }
+ break;
}
}
- private bool m_ShuttingDown;
- private bool m_StopProcessingMessages;
+ ///
+ /// The client id used to represent the server
+ ///
+ public const ulong ServerClientId = 0;
+
+ ///
+ /// Returns ServerClientId if IsServer or LocalClientId if not
+ ///
+ public ulong LocalClientId
+ {
+ get => ConnectionManager.LocalClient.ClientId;
+ internal set => ConnectionManager.LocalClient.ClientId = value;
+ }
+
+ ///
+ /// Gets a dictionary of connected clients and their clientId keys. This is only accessible on the server.
+ ///
+ public IReadOnlyDictionary ConnectedClients => IsServer ? ConnectionManager.ConnectedClients : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClients)} should only be accessed on server.");
+
+ ///
+ /// Gets a list of connected clients. This is only accessible on the server.
+ ///
+ public IReadOnlyList ConnectedClientsList => IsServer ? ConnectionManager.ConnectedClientsList : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientsList)} should only be accessed on server.");
+
+ ///
+ /// Gets a list of just the IDs of all connected clients. This is only accessible on the server.
+ ///
+ public IReadOnlyList ConnectedClientsIds => IsServer ? ConnectionManager.ConnectedClientIds : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientIds)} should only be accessed on server.");
+
+ ///
+ /// Gets the local for this client.
+ ///
+ public NetworkClient LocalClient => ConnectionManager.LocalClient;
+
+ ///
+ /// Gets a dictionary of the clients that have been accepted by the transport but are still pending by the Netcode. This is only populated on the server.
+ ///
+ // See NetworkConnectionManager.AddPendingClient and NetworkConnectionManager.RemovePendingClient to see how this is now populated
+ public readonly Dictionary PendingClients = new Dictionary();
+
+ ///
+ /// Gets Whether or not a server is running
+ ///
+ public bool IsServer => ConnectionManager.LocalClient.IsServer;
+
+ ///
+ /// Gets Whether or not a client is running
+ ///
+ public bool IsClient => ConnectionManager.LocalClient.IsClient;
+
+ ///
+ /// Gets if we are running as host
+ ///
+ public bool IsHost => ConnectionManager.LocalClient.IsHost;
///
/// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
/// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
///
- public string DisconnectReason { get; internal set; }
+ public string DisconnectReason => ConnectionManager.DisconnectReason;
- private class NetworkManagerHooks : INetworkHooks
+ ///
+ /// Is true when a server or host is listening for connections.
+ /// Is true when a client is connecting or connected to a network session.
+ /// Is false when not listening, connecting, or connected.
+ ///
+ public bool IsListening
{
- private NetworkManager m_NetworkManager;
-
- internal NetworkManagerHooks(NetworkManager manager)
- {
- m_NetworkManager = manager;
- }
-
- public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
- {
- }
-
- public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
- {
- }
-
- public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
- {
- }
-
- public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
- {
- }
-
- public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
- {
- }
-
- public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
- {
- }
-
- public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
- {
- }
-
- public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
- {
- }
-
- public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
- {
- return !m_NetworkManager.m_StopProcessingMessages;
- }
-
- public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
- {
- if (m_NetworkManager.IsServer)
- {
- if (messageType == typeof(ConnectionApprovedMessage))
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
- }
- return false;
- }
- if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
- (client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted");
- }
-
- return false;
- }
-
- if (m_NetworkManager.ConnectedClients.TryGetValue(senderId, out NetworkClient connectedClient) && messageType == typeof(ConnectionRequestMessage))
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
- }
-
- return false;
- }
- }
- else
- {
- if (messageType == typeof(ConnectionRequestMessage))
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
- }
- return false;
- }
- if (m_NetworkManager.IsConnectedClient && messageType == typeof(ConnectionApprovedMessage))
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
- }
- return false;
- }
- }
-
- return !m_NetworkManager.m_StopProcessingMessages;
- }
-
- public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage
- {
- }
-
- public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage
- {
- }
+ get => ConnectionManager.IsListening;
+ internal set => ConnectionManager.IsListening = value;
}
- private class NetworkManagerMessageSender : IMessageSender
+ ///
+ /// When true, the client is connected, approved, and synchronized with
+ /// the server.
+ ///
+ ///
+ ///
+ public bool IsConnectedClient
{
- private NetworkManager m_NetworkManager;
+ get => ConnectionManager.LocalClient.IsConnected;
+ internal set => ConnectionManager.LocalClient.IsConnected = value;
+ }
- public NetworkManagerMessageSender(NetworkManager manager)
+ ///
+ /// Is true when the client has been approved.
+ ///
+ ///
+ /// This only reflects the client's approved status and does not mean the client
+ /// has finished the connection and synchronization process. The server-host will
+ /// always be approved upon being starting the
+ ///
+ ///
+ public bool IsApproved
+ {
+ get => ConnectionManager.LocalClient.IsApproved;
+ internal set => ConnectionManager.LocalClient.IsApproved = value;
+ }
+
+ ///
+ /// The callback to invoke if the fails.
+ ///
+ ///
+ /// A failure of the transport is always followed by the shutting down. Recovering
+ /// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
+ /// recreating a new service allocation depending on the transport) and restarting the client/server/host.
+ ///
+ public event Action OnTransportFailure
+ {
+ add => ConnectionManager.OnTransportFailure += value;
+ remove => ConnectionManager.OnTransportFailure -= value;
+ }
+
+ ///
+ /// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
+ ///
+ public Action ConnectionApprovalCallback
+ {
+ get => ConnectionManager.ConnectionApprovalCallback;
+ set
{
- m_NetworkManager = manager;
- }
+ if (value != null && value.GetInvocationList().Length > 1)
+ {
+ throw new InvalidOperationException($"Only one {nameof(ConnectionApprovalCallback)} can be registered at a time.");
+ }
- public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
- {
- var sendBuffer = batchData.ToTempByteArray();
-
- m_NetworkManager.NetworkConfig.NetworkTransport.Send(m_NetworkManager.ClientIdToTransportId(clientId), sendBuffer, delivery);
+ ConnectionManager.ConnectionApprovalCallback = value;
}
}
///
- /// Returns the to use as the override as could be defined within the NetworkPrefab list
- /// Note: This should be used to create pools (with components)
- /// under the scenario where you are using the Host model as it spawns everything locally. As such, the override
- /// will not be applied when spawning locally on a Host.
- /// Related Classes and Interfaces:
- ///
- ///
+ /// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
///
- /// the to be checked for a defined NetworkPrefab override
- /// a that is either the override or if no overrides exist it returns the same as the one passed in as a parameter
- public GameObject GetNetworkPrefabOverride(GameObject gameObject)
+ public event Action OnClientConnectedCallback
{
- if (gameObject.TryGetComponent(out var networkObject))
- {
- if (NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
- {
- switch (NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].Override)
- {
- case NetworkPrefabOverride.Hash:
- case NetworkPrefabOverride.Prefab:
- {
- return NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab;
- }
- }
- }
- }
- return gameObject;
+ add => ConnectionManager.OnClientConnectedCallback += value;
+ remove => ConnectionManager.OnClientConnectedCallback -= value;
}
///
- /// Accessor for the of the NetworkManager.
- /// Prefer the use of the LocalTime and ServerTime properties
+ /// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects.
///
- public NetworkTimeSystem NetworkTimeSystem { get; private set; }
+ public event Action OnClientDisconnectCallback
+ {
+ add => ConnectionManager.OnClientDisconnectCallback += value;
+ remove => ConnectionManager.OnClientDisconnectCallback -= value;
+ }
///
- /// Accessor for the of the NetworkManager.
+ /// The current host name we are connected to, used to validate certificate
///
- public NetworkTickSystem NetworkTickSystem { get; private set; }
+ public string ConnectedHostname => string.Empty;
+
+ ///
+ /// Connection Approval Response
+ ///
+ public class ConnectionApprovalResponse
+ {
+ ///
+ /// Whether or not the client was approved
+ ///
+ public bool Approved;
+
+ ///
+ /// If true, a player object will be created. Otherwise the client will have no object.
+ ///
+ public bool CreatePlayerObject;
+
+ ///
+ /// The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.
+ ///
+ public uint? PlayerPrefabHash;
+
+ ///
+ /// The position to spawn the client at. If null, the prefab position is used.
+ ///
+ public Vector3? Position;
+
+ ///
+ /// The rotation to spawn the client with. If null, the prefab position is used.
+ ///
+ public Quaternion? Rotation;
+
+ ///
+ /// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.
+ ///
+ public bool Pending;
+
+ ///
+ /// Optional reason. If Approved is false, this reason will be sent to the client so they know why they
+ /// were not approved.
+ ///
+ public string Reason;
+ }
+
+ ///
+ /// Connection Approval Request
+ ///
+ public struct ConnectionApprovalRequest
+ {
+ ///
+ /// The connection data payload
+ ///
+ public byte[] Payload;
+
+ ///
+ /// The Network Id of the client we are about to handle
+ ///
+ public ulong ClientNetworkId;
+ }
+
+ ///
+ /// Can be used to determine if the is currently shutting itself down
+ ///
+ public bool ShutdownInProgress => m_ShuttingDown;
+
+ private bool m_ShuttingDown;
+
+ ///
+ /// The current netcode project configuration
+ ///
+ [HideInInspector]
+ public NetworkConfig NetworkConfig;
///
/// The local
@@ -291,164 +326,7 @@ namespace Unity.Netcode
///
public static NetworkManager Singleton { get; private set; }
- ///
- /// Gets the SpawnManager for this NetworkManager
- ///
- public NetworkSpawnManager SpawnManager { get; private set; }
-
- internal IDeferredMessageManager DeferredMessageManager { get; private set; }
-
- internal IRealTimeProvider RealTimeProvider { get; private set; }
-
- ///
- /// Gets the CustomMessagingManager for this NetworkManager
- ///
- public CustomMessagingManager CustomMessagingManager { get; private set; }
-
- ///
- /// The instance created after starting the
- ///
- public NetworkSceneManager SceneManager { get; private set; }
-
- ///
- /// The client id used to represent the server
- ///
- public const ulong ServerClientId = 0;
-
- ///
- /// Gets the networkId of the server
- ///
- private ulong m_ServerTransportId => NetworkConfig.NetworkTransport?.ServerClientId ?? throw new NullReferenceException($"The transport in the active {nameof(NetworkConfig)} is null");
-
- ///
- /// Returns ServerClientId if IsServer or LocalClientId if not
- ///
- public ulong LocalClientId
- {
- get => m_LocalClientId;
- internal set => m_LocalClientId = value;
- }
-
- private ulong m_LocalClientId;
-
- private Dictionary m_ConnectedClients = new Dictionary();
-
- private ulong m_NextClientId = 1;
- private Dictionary m_ClientIdToTransportIdMap = new Dictionary();
- private Dictionary m_TransportIdToClientIdMap = new Dictionary();
-
- private List m_ConnectedClientsList = new List();
-
- private List m_ConnectedClientIds = new List();
-
- ///
- /// Gets a dictionary of connected clients and their clientId keys. This is only accessible on the server.
- ///
- public IReadOnlyDictionary ConnectedClients
- {
- get
- {
- if (IsServer == false)
- {
- throw new NotServerException($"{nameof(ConnectedClients)} should only be accessed on server.");
- }
- return m_ConnectedClients;
- }
- }
-
- ///
- /// Gets a list of connected clients. This is only accessible on the server.
- ///
- public IReadOnlyList ConnectedClientsList
- {
- get
- {
- if (IsServer == false)
- {
- throw new NotServerException($"{nameof(ConnectedClientsList)} should only be accessed on server.");
- }
- return m_ConnectedClientsList;
- }
- }
-
- ///
- /// Gets a list of just the IDs of all connected clients. This is only accessible on the server.
- ///
- public IReadOnlyList ConnectedClientsIds
- {
- get
- {
- if (IsServer == false)
- {
- throw new NotServerException($"{nameof(m_ConnectedClientIds)} should only be accessed on server.");
- }
- return m_ConnectedClientIds;
- }
- }
-
- ///
- /// Gets the local for this client.
- ///
- public NetworkClient LocalClient { get; internal set; }
-
- ///
- /// Gets a dictionary of the clients that have been accepted by the transport but are still pending by the Netcode. This is only populated on the server.
- ///
- public readonly Dictionary PendingClients = new Dictionary();
-
- ///
- /// Gets Whether or not a server is running
- ///
- public bool IsServer { get; internal set; }
-
- ///
- /// Gets Whether or not a client is running
- ///
- public bool IsClient { get; internal set; }
-
- ///
- /// Gets if we are running as host
- ///
- public bool IsHost => IsServer && IsClient;
-
- ///
- /// Gets Whether or not we are listening for connections
- ///
- public bool IsListening { get; internal set; }
-
- ///
- /// When true, the client is connected, approved, and synchronized with
- /// the server.
- ///
- public bool IsConnectedClient { get; internal set; }
-
- ///
- /// Is true when the client has been approved.
- ///
- ///
- /// This only reflects the client's approved status and does not mean the client
- /// has finished the connection and synchronization process. The server-host will
- /// always be approved upon being starting the
- ///
- ///
- public bool IsApproved { get; internal set; }
-
- ///
- /// Can be used to determine if the is currently shutting itself down
- ///
- 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.
- ///
- public event Action OnClientConnectedCallback = null;
-
- internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId);
-
- ///
- /// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects.
- ///
- public event Action OnClientDisconnectCallback = null;
+ internal static event Action OnSingletonReady;
///
/// This callback is invoked when the local server is started and listening for incoming connections.
@@ -474,117 +352,85 @@ namespace Unity.Netcode
public event Action OnClientStopped = null;
///
- /// The callback to invoke if the fails.
+ /// The instance created after starting the
///
- ///
- /// A failure of the transport is always followed by the shutting down. Recovering
- /// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
- /// recreating a new service allocation depending on the transport) and restarting the client/server/host.
- ///
- public event Action OnTransportFailure = null;
-
- ///
- /// Connection Approval Response
- ///
- public class ConnectionApprovalResponse
+ public NetworkPrefabHandler PrefabHandler
{
- ///
- /// Whether or not the client was approved
- ///
- public bool Approved;
- ///
- /// If true, a player object will be created. Otherwise the client will have no object.
- ///
- public bool CreatePlayerObject;
- ///
- /// The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.
- ///
- public uint? PlayerPrefabHash;
- ///
- /// The position to spawn the client at. If null, the prefab position is used.
- ///
- public Vector3? Position;
- ///
- /// The rotation to spawn the client with. If null, the prefab position is used.
- ///
- public Quaternion? Rotation;
- ///
- /// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.
- ///
- public bool Pending;
-
- ///
- /// Optional reason. If Approved is false, this reason will be sent to the client so they know why they
- /// were not approved.
- ///
- public string Reason;
- }
-
- ///
- /// Connection Approval Request
- ///
- public struct ConnectionApprovalRequest
- {
- ///
- /// The connection data payload
- ///
- public byte[] Payload;
- ///
- /// The Network Id of the client we are about to handle
- ///
- public ulong ClientNetworkId;
- }
-
- ///
- /// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
- ///
- public Action ConnectionApprovalCallback
- {
- get => m_ConnectionApprovalCallback;
- set
+ get
{
- if (value != null && value.GetInvocationList().Length > 1)
+ if (m_PrefabHandler == null)
{
- throw new InvalidOperationException($"Only one {nameof(ConnectionApprovalCallback)} can be registered at a time.");
- }
- else
- {
- m_ConnectionApprovalCallback = value;
+ m_PrefabHandler = new NetworkPrefabHandler();
+ m_PrefabHandler.Initialize(this);
}
+
+ return m_PrefabHandler;
}
}
- private Action m_ConnectionApprovalCallback;
+ private NetworkPrefabHandler m_PrefabHandler;
///
- /// The current NetworkConfig
+ /// Gets the SpawnManager for this NetworkManager
///
- [HideInInspector]
- public NetworkConfig NetworkConfig;
+ public NetworkSpawnManager SpawnManager { get; private set; }
+
+ internal IDeferredNetworkMessageManager DeferredMessageManager { get; private set; }
///
- /// The current host name we are connected to, used to validate certificate
+ /// Gets the CustomMessagingManager for this NetworkManager
///
- public string ConnectedHostname { get; private set; }
+ public CustomMessagingManager CustomMessagingManager { get; private set; }
- internal INetworkMetrics NetworkMetrics { get; private set; }
+ ///
+ /// The instance created after starting the
+ ///
+ public NetworkSceneManager SceneManager { get; private set; }
- internal static event Action OnSingletonReady;
+ internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
+
+ ///
+ /// Accessor property for the of the NetworkManager.
+ /// Prefer the use of the LocalTime and ServerTime properties
+ ///
+ public NetworkTimeSystem NetworkTimeSystem { get; private set; }
+
+ ///
+ /// Accessor property for the of the NetworkManager.
+ ///
+ public NetworkTickSystem NetworkTickSystem { get; private set; }
+
+ ///
+ /// Used for time mocking in tests
+ ///
+ internal IRealTimeProvider RealTimeProvider { get; private set; }
+
+ internal INetworkMetrics NetworkMetrics => MetricsManager.NetworkMetrics;
+ internal NetworkMetricsManager MetricsManager = new NetworkMetricsManager();
+ internal NetworkConnectionManager ConnectionManager = new NetworkConnectionManager();
+ internal NetworkMessageManager MessageManager = null;
#if UNITY_EDITOR
+ internal static INetworkManagerHelper NetworkManagerHelper;
+
+ ///
+ /// Interface for NetworkManagerHelper
+ ///
+ internal interface INetworkManagerHelper
+ {
+ bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false);
+ void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false);
+ }
+
internal delegate void ResetNetworkManagerDelegate(NetworkManager manager);
internal static ResetNetworkManagerDelegate OnNetworkManagerReset;
-#endif
private void Reset()
{
-#if UNITY_EDITOR
OnNetworkManagerReset?.Invoke(this);
-#endif
}
-#if UNITY_EDITOR
internal void OnValidate()
{
if (NetworkConfig == null)
@@ -627,7 +473,7 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
- NetworkLog.LogError($"Cannot register {PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root");
+ NetworkLog.LogError($"Cannot register {NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root");
}
continue;
@@ -640,479 +486,13 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
- NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)");
+ NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)");
}
}
}
}
}
#endif
- ///
- /// Adds a new prefab to the network prefab list.
- /// This can be any GameObject with a NetworkObject component, from any source (addressables, asset
- /// bundles, Resource.Load, dynamically created, etc)
- ///
- /// There are three limitations to this method:
- /// - If you have NetworkConfig.ForceSamePrefabs enabled, you can only do this before starting
- /// networking, and the server and all connected clients must all have the same exact set of prefabs
- /// added via this method before connecting
- /// - Adding a prefab on the server does not automatically add it on the client - it's up to you
- /// to make sure the client and server are synchronized via whatever method makes sense for your game
- /// (RPCs, configs, deterministic loading, etc)
- /// - If the server sends a Spawn message to a client that has not yet added a prefab for, the spawn message
- /// and any other relevant messages will be held for a configurable time (default 1 second, configured via
- /// NetworkConfig.SpawnTimeout) before an error is logged. This is intented to enable the SDK to gracefully
- /// handle unexpected conditions (slow disks, slow network, etc) that slow down asset loading. This timeout
- /// should not be relied on and code shouldn't be written around it - your code should be written so that
- /// the asset is expected to be loaded before it's needed.
- ///
- ///
- ///
- public void AddNetworkPrefab(GameObject prefab)
- {
- if (IsListening && NetworkConfig.ForceSamePrefabs)
- {
- throw new Exception($"All prefabs must be registered before starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
- }
-
- var networkObject = prefab.GetComponent();
- if (!networkObject)
- {
- throw new Exception($"All {nameof(NetworkPrefab)}s must contain a {nameof(NetworkObject)} component.");
- }
-
- var networkPrefab = new NetworkPrefab { Prefab = prefab };
- bool added = NetworkConfig.Prefabs.Add(networkPrefab);
- if (IsListening && added)
- {
- DeferredMessageManager.ProcessTriggers(IDeferredMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash);
- }
- }
-
- ///
- /// Remove a prefab from the prefab list.
- /// As with AddNetworkPrefab, this is specific to the client it's called on -
- /// calling it on the server does not automatically remove anything on any of the
- /// client processes.
- ///
- /// Like AddNetworkPrefab, when NetworkConfig.ForceSamePrefabs is enabled,
- /// this cannot be called after connecting.
- ///
- ///
- public void RemoveNetworkPrefab(GameObject prefab)
- {
- if (IsListening && NetworkConfig.ForceSamePrefabs)
- {
- throw new Exception($"Prefabs cannot be removed after starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
- }
-
- var globalObjectIdHash = prefab.GetComponent().GlobalObjectIdHash;
- NetworkConfig.Prefabs.Remove(prefab);
- if (PrefabHandler.ContainsHandler(globalObjectIdHash))
- {
- PrefabHandler.RemoveHandler(globalObjectIdHash);
- }
- }
-
- internal void Initialize(bool server)
- {
- // Don't allow the user to start a network session if the NetworkManager is
- // still parented under another GameObject
- if (NetworkManagerCheckForParent(true))
- {
- return;
- }
-
- DisconnectReason = string.Empty;
- IsApproved = false;
-
- ComponentFactory.SetDefaults();
-
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(Initialize));
- }
-
- this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
- this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
-
- MessagingSystem = new MessagingSystem(new NetworkManagerMessageSender(this), this);
-
- MessagingSystem.Hook(new NetworkManagerHooks(this));
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- MessagingSystem.Hook(new ProfilingHooks());
-#endif
-
-#if MULTIPLAYER_TOOLS
- MessagingSystem.Hook(new MetricHooks(this));
-#endif
- LocalClientId = ulong.MaxValue;
-
- ClearClients();
-
- // Create spawn manager instance
- SpawnManager = new NetworkSpawnManager(this);
-
- DeferredMessageManager = ComponentFactory.Create(this);
-
- RealTimeProvider = ComponentFactory.Create(this);
-
- CustomMessagingManager = new CustomMessagingManager(this);
-
- SceneManager = new NetworkSceneManager(this);
-
- BehaviourUpdater = new NetworkBehaviourUpdater();
-
-
- if (NetworkMetrics == null)
- {
-#if MULTIPLAYER_TOOLS
- NetworkMetrics = new NetworkMetrics();
-#else
- NetworkMetrics = new NullNetworkMetrics();
-#endif
- }
-
-#if MULTIPLAYER_TOOLS
- NetworkSolutionInterface.SetInterface(new NetworkSolutionInterfaceParameters
- {
- NetworkObjectProvider = new NetworkObjectProvider(this),
- });
-#endif
-
- if (NetworkConfig.NetworkTransport == null)
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
- {
- NetworkLog.LogError("No transport has been selected!");
- }
-
- return;
- }
-
- NetworkConfig.NetworkTransport.NetworkMetrics = NetworkMetrics;
-
- if (server)
- {
- NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
- }
- else
- {
- NetworkTimeSystem = new NetworkTimeSystem(1.0 / NetworkConfig.TickRate, k_DefaultBufferSizeSec, 0.2);
- }
-
- NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0);
- NetworkTickSystem.Tick += OnNetworkManagerTick;
-
- this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
-
- NetworkConfig.InitializePrefabs();
-
- // If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
- if (NetworkConfig.PlayerPrefab != null)
- {
- if (NetworkConfig.PlayerPrefab.TryGetComponent(out var playerPrefabNetworkObject))
- {
- //In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
- if (!NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject
- .GlobalObjectIdHash))
- {
- //Then add a new entry for the player prefab
- AddNetworkPrefab(NetworkConfig.PlayerPrefab);
- }
- }
- else
- {
- // Provide the name of the prefab with issues so the user can more easily find the prefab and fix it
- Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!.");
- }
- }
-
- NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
-
- NetworkConfig.NetworkTransport.Initialize(this);
- }
-
- private void ClearClients()
- {
- PendingClients.Clear();
- m_ConnectedClients.Clear();
- m_ConnectedClientsList.Clear();
- m_ConnectedClientIds.Clear();
- LocalClient = null;
- NetworkObject.OrphanChildren.Clear();
- ClientsToApprove.Clear();
- }
-
- ///
- /// Starts a server
- ///
- /// (/) returns true if started in server mode successfully.
- public bool StartServer()
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(StartServer));
- }
-
- if (!CanStart(StartType.Server))
- {
- return false;
- }
-
- Initialize(true);
- IsServer = true;
- IsClient = false;
- IsListening = true;
- LocalClientId = ServerClientId;
-
- try
- {
- // If we failed to start then shutdown and notify user that the transport failed to start
- if (NetworkConfig.NetworkTransport.StartServer())
- {
- SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
-
- OnServerStarted?.Invoke();
- IsApproved = true;
- return true;
- }
- else
- {
- IsServer = false;
- IsClient = false;
- IsListening = false;
-
- Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
- OnTransportFailure?.Invoke();
- Shutdown();
- }
- }
- catch (Exception)
- {
- IsServer = false;
- IsClient = false;
- IsListening = false;
- throw;
- }
-
- return false;
- }
-
- ///
- /// Starts a client
- ///
- /// (/) returns true if started in client mode successfully.
- public bool StartClient()
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(StartClient));
- }
-
- if (!CanStart(StartType.Client))
- {
- return false;
- }
-
- Initialize(false);
- MessagingSystem.ClientConnected(ServerClientId);
-
- if (!NetworkConfig.NetworkTransport.StartClient())
- {
- Debug.LogError($"Client is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
- OnTransportFailure?.Invoke();
- Shutdown();
- return false;
- }
-
- IsServer = false;
- IsClient = true;
- IsListening = true;
-
- OnClientStarted?.Invoke();
- return true;
- }
-
- ///
- /// Starts a Host
- ///
- /// (/) returns true if started in host mode successfully.
- public bool StartHost()
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(StartHost));
- }
-
- if (!CanStart(StartType.Host))
- {
- return false;
- }
-
- Initialize(true);
-
- IsServer = true;
- IsClient = true;
- IsListening = true;
-
- try
- {
- // If we failed to start then shutdown and notify user that the transport failed to start
- if (!NetworkConfig.NetworkTransport.StartServer())
- {
- Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
- OnTransportFailure?.Invoke();
- Shutdown();
-
- IsServer = false;
- IsClient = false;
- IsListening = false;
-
- return false;
- }
- }
- catch (Exception)
- {
- IsServer = false;
- IsClient = false;
- IsListening = false;
- throw;
- }
-
- MessagingSystem.ClientConnected(ServerClientId);
- LocalClientId = ServerClientId;
- NetworkMetrics.SetConnectionId(LocalClientId);
-
- if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
- {
- var response = new ConnectionApprovalResponse();
- ConnectionApprovalCallback(new ConnectionApprovalRequest { Payload = NetworkConfig.ConnectionData, ClientNetworkId = ServerClientId }, response);
- if (!response.Approved)
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning("You cannot decline the host connection. The connection was automatically approved.");
- }
- }
-
- response.Approved = true;
- IsApproved = true;
- HandleConnectionApproval(ServerClientId, response);
- }
- else
- {
- var response = new ConnectionApprovalResponse
- {
- Approved = true,
- CreatePlayerObject = NetworkConfig.PlayerPrefab != null
- };
- HandleConnectionApproval(ServerClientId, response);
- }
-
- SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
-
- OnServerStarted?.Invoke();
- OnClientStarted?.Invoke();
-
- // This assures that any in-scene placed NetworkObject is spawned and
- // any associated NetworkBehaviours' netcode related properties are
- // set prior to invoking OnClientConnected.
- InvokeOnClientConnectedCallback(LocalClientId);
-
- return true;
- }
-
- private enum StartType
- {
- Server,
- Host,
- Client
- }
-
- private bool CanStart(StartType type)
- {
- if (IsListening)
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning("Cannot start " + type + " while an instance is already running");
- }
-
- return false;
- }
-
- // Only if it is starting as a server or host do we need to check this
- // Clients don't invoke the ConnectionApprovalCallback
- if (NetworkConfig.ConnectionApproval && type != StartType.Client)
- {
- if (ConnectionApprovalCallback == null)
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning(
- "No ConnectionApproval callback defined. Connection approval will timeout");
- }
- }
- }
-
- if (ConnectionApprovalCallback != null)
- {
- if (!NetworkConfig.ConnectionApproval)
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning(
- "A ConnectionApproval callback is defined but ConnectionApproval is disabled. In order to use ConnectionApproval it has to be explicitly enabled ");
- }
- }
- }
-
- return true;
- }
-
- ///
- /// Set this NetworkManager instance as the static NetworkManager singleton
- ///
- public void SetSingleton()
- {
- Singleton = this;
-
- OnSingletonReady?.Invoke();
- }
-
- private void OnEnable()
- {
- if (RunInBackground)
- {
- Application.runInBackground = true;
- }
-
- if (Singleton == null)
- {
- SetSingleton();
- }
-
- if (!NetworkManagerCheckForParent())
- {
- DontDestroyOnLoad(gameObject);
- }
- }
-
- private void Awake()
- {
- NetworkConfig?.InitializePrefabs();
-
- UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
- }
-
- ///
- /// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject
- ///
- private void OnTransformParentChanged()
- {
- NetworkManagerCheckForParent();
- }
///
/// Determines if the NetworkManager's GameObject is parented under another GameObject and
@@ -1137,53 +517,409 @@ namespace Unity.Netcode
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
}
-#if UNITY_EDITOR
- internal static INetworkManagerHelper NetworkManagerHelper;
///
- /// Interface for NetworkManagerHelper
+ /// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject
///
- internal interface INetworkManagerHelper
+ private void OnTransformParentChanged()
{
- bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false);
- void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false);
+ NetworkManagerCheckForParent();
}
+
+ ///
+ /// Set this NetworkManager instance as the static NetworkManager singleton
+ ///
+ public void SetSingleton()
+ {
+ Singleton = this;
+
+ OnSingletonReady?.Invoke();
+ }
+
+ private void Awake()
+ {
+ NetworkConfig?.InitializePrefabs();
+
+ UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
+ }
+
+ private void OnEnable()
+ {
+ if (RunInBackground)
+ {
+ Application.runInBackground = true;
+ }
+
+ if (Singleton == null)
+ {
+ SetSingleton();
+ }
+
+ if (!NetworkManagerCheckForParent())
+ {
+ DontDestroyOnLoad(gameObject);
+ }
+ }
+
+ ///
+ ///
+ ///
+ /// the to be checked for a defined NetworkPrefab override
+ /// a that is either the override or if no overrides exist it returns the same as the one passed in as a parameter
+ public GameObject GetNetworkPrefabOverride(GameObject gameObject) => PrefabHandler.GetNetworkPrefabOverride(gameObject);
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void AddNetworkPrefab(GameObject prefab) => PrefabHandler.AddNetworkPrefab(prefab);
+
+ ///
+ ///
+ ///
+ ///
+ public void RemoveNetworkPrefab(GameObject prefab) => PrefabHandler.RemoveNetworkPrefab(prefab);
+
+ ///
+ /// Sets the maximum size of a single non-fragmented message (or message batch) passed through the transport.
+ /// This should represent the transport's MTU size, minus any transport-level overhead.
+ ///
+ ///
+ public int MaximumTransmissionUnitSize
+ {
+ set => MessageManager.NonFragmentedMessageMaxSize = value;
+ get => MessageManager.NonFragmentedMessageMaxSize;
+ }
+
+ ///
+ /// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery.
+ /// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size.
+ ///
+ ///
+ public int MaximumFragmentedMessageSize
+ {
+ set => MessageManager.FragmentedMessageMaxSize = value;
+ get => MessageManager.FragmentedMessageMaxSize;
+ }
+
+ internal void Initialize(bool server)
+ {
+ // Don't allow the user to start a network session if the NetworkManager is
+ // still parented under another GameObject
+ if (NetworkManagerCheckForParent(true))
+ {
+ return;
+ }
+
+ if (NetworkConfig.NetworkTransport == null)
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
+ {
+ NetworkLog.LogError("No transport has been selected!");
+ }
+
+ return;
+ }
+
+ // Logging initializes first for any logging during systems initialization
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo(nameof(Initialize));
+ }
+
+ this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
+ this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
+ this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
+
+ // ComponentFactory needs to set its defaults next
+ ComponentFactory.SetDefaults();
+
+ // UnityTransport dependencies are then initialized
+ RealTimeProvider = ComponentFactory.Create(this);
+ MetricsManager.Initialize(this);
+
+ {
+ MessageManager = new NetworkMessageManager(new DefaultMessageSender(this), this);
+
+ MessageManager.Hook(new NetworkManagerHooks(this));
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ MessageManager.Hook(new ProfilingHooks());
#endif
- // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
- private void OnSceneUnloaded(Scene scene)
- {
- if (scene == gameObject.scene)
- {
- OnDestroy();
+#if MULTIPLAYER_TOOLS
+ MessageManager.Hook(new MetricHooks(this));
+#endif
+
+ // Assures there is a server message queue available
+ MessageManager.ClientConnected(ServerClientId);
}
+
+ // Now the connection manager can initialize (which initializes transport)
+ ConnectionManager.Initialize(this);
+
+ // The remaining systems can then be initialized
+ NetworkTimeSystem = server ? NetworkTimeSystem.ServerTimeSystem() : new NetworkTimeSystem(1.0 / NetworkConfig.TickRate);
+ NetworkTickSystem = NetworkTimeSystem.Initialize(this);
+
+ // Create spawn manager instance
+ SpawnManager = new NetworkSpawnManager(this);
+
+ DeferredMessageManager = ComponentFactory.Create(this);
+
+ CustomMessagingManager = new CustomMessagingManager(this);
+
+ SceneManager = new NetworkSceneManager(this);
+
+ BehaviourUpdater = new NetworkBehaviourUpdater();
+ BehaviourUpdater.Initialize(this);
+
+ NetworkConfig.InitializePrefabs();
+ PrefabHandler.RegisterPlayerPrefab();
}
- // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application.
- private void OnApplicationQuit()
+ private enum StartType
{
- OnDestroy();
+ Server,
+ Host,
+ Client
}
- // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
- private void OnDestroy()
+ ///
+ /// Determines if NetworkManager can start based on the current
+ /// NetworkManager instance state(s)
+ ///
+ private bool CanStart(StartType type)
{
- ShutdownInternal();
-
- UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
-
- if (Singleton == this)
+ if (IsListening)
{
- Singleton = null;
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning("Cannot start " + type + " while an instance is already running");
+ }
+
+ return false;
}
+
+ // Only if it is starting as a server or host do we need to check this
+ // Clients don't invoke the ConnectionApprovalCallback
+ if (NetworkConfig.ConnectionApproval && type != StartType.Client)
+ {
+ if (ConnectionApprovalCallback == null)
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning("No ConnectionApproval callback defined. Connection approval will timeout");
+ }
+ }
+ }
+
+ if (ConnectionApprovalCallback != null)
+ {
+ if (!NetworkConfig.ConnectionApproval)
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning("A ConnectionApproval callback is defined but ConnectionApproval is disabled. In order to use ConnectionApproval it has to be explicitly enabled ");
+ }
+ }
+ }
+
+ return true;
}
- private void DisconnectRemoteClient(ulong clientId)
+ ///
+ /// Starts a server
+ ///
+ /// (/) returns true if started in server mode successfully.
+ public bool StartServer()
{
- var transportId = ClientIdToTransportId(clientId);
- MessagingSystem.ProcessSendQueues();
- NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo(nameof(StartServer));
+ }
+
+ if (!CanStart(StartType.Server))
+ {
+ return false;
+ }
+
+ ConnectionManager.LocalClient.SetRole(true, false, this);
+ ConnectionManager.LocalClient.ClientId = ServerClientId;
+
+ Initialize(true);
+
+ try
+ {
+ IsListening = NetworkConfig.NetworkTransport.StartServer();
+ // If we failed to start then shutdown and notify user that the transport failed to start
+ if (IsListening)
+ {
+ SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
+
+ OnServerStarted?.Invoke();
+ ConnectionManager.LocalClient.IsApproved = true;
+ return true;
+ }
+
+ ConnectionManager.TransportFailureEventHandler(true);
+ }
+ catch (Exception)
+ {
+ ConnectionManager.LocalClient.SetRole(false, false);
+ IsListening = false;
+ throw;
+ }
+
+ return IsListening;
}
+ ///
+ /// Starts a client
+ ///
+ /// (/) returns true if started in client mode successfully.
+ public bool StartClient()
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo(nameof(StartClient));
+ }
+
+ if (!CanStart(StartType.Client))
+ {
+ return false;
+ }
+
+ ConnectionManager.LocalClient.SetRole(false, true, this);
+
+ Initialize(false);
+
+ try
+ {
+ IsListening = NetworkConfig.NetworkTransport.StartClient();
+ // If we failed to start then shutdown and notify user that the transport failed to start
+ if (!IsListening)
+ {
+ ConnectionManager.TransportFailureEventHandler(true);
+ }
+ else
+ {
+ OnClientStarted?.Invoke();
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.LogException(ex);
+ ConnectionManager.LocalClient.SetRole(false, false);
+ IsListening = false;
+ }
+
+ return IsListening;
+ }
+
+ ///
+ /// Starts a Host
+ ///
+ /// (/) returns true if started in host mode successfully.
+ public bool StartHost()
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo(nameof(StartHost));
+ }
+
+ if (!CanStart(StartType.Host))
+ {
+ return false;
+ }
+
+ ConnectionManager.LocalClient.SetRole(true, true, this);
+
+ Initialize(true);
+
+ try
+ {
+ IsListening = NetworkConfig.NetworkTransport.StartServer();
+ // If we failed to start then shutdown and notify user that the transport failed to start
+ if (!IsListening)
+ {
+ ConnectionManager.TransportFailureEventHandler(true);
+ }
+ else
+ {
+ // Finalize host-client and server creation logic
+ HostServerInitialize();
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.LogException(ex);
+ ConnectionManager.LocalClient.SetRole(false, false);
+ IsListening = false;
+ }
+
+ return IsListening;
+ }
+
+ ///
+ /// Handles the host client creation logic along with
+ /// additional server creation logic
+ ///
+ private void HostServerInitialize()
+ {
+ LocalClientId = ServerClientId;
+ NetworkMetrics.SetConnectionId(LocalClientId);
+
+ if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
+ {
+ var response = new ConnectionApprovalResponse();
+ ConnectionApprovalCallback(new ConnectionApprovalRequest { Payload = NetworkConfig.ConnectionData, ClientNetworkId = ServerClientId }, response);
+ if (!response.Approved)
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning("You cannot decline the host connection. The connection was automatically approved.");
+ }
+ }
+
+ response.Approved = true;
+ ConnectionManager.HandleConnectionApproval(ServerClientId, response);
+ }
+ else
+ {
+ var response = new ConnectionApprovalResponse
+ {
+ Approved = true,
+ CreatePlayerObject = NetworkConfig.PlayerPrefab != null
+ };
+ ConnectionManager.HandleConnectionApproval(ServerClientId, response);
+ }
+
+ SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
+
+ OnServerStarted?.Invoke();
+ OnClientStarted?.Invoke();
+
+ // This assures that any in-scene placed NetworkObject is spawned and
+ // any associated NetworkBehaviours' netcode related properties are
+ // set prior to invoking OnClientConnected.
+ ConnectionManager.InvokeOnClientConnectedCallback(LocalClientId);
+ }
+
+ ///
+ /// Disconnects the remote client.
+ ///
+ /// The ClientId to disconnect
+ public void DisconnectClient(ulong clientId) => ConnectionManager.DisconnectClient(clientId);
+
+ ///
+ /// Disconnects the remote client.
+ ///
+ /// The ClientId to disconnect
+ /// Disconnection reason. If set, client will receive a DisconnectReasonMessage and have the
+ /// reason available in the NetworkManager.DisconnectReason property
+ public void DisconnectClient(ulong clientId, string reason = null) => ConnectionManager.DisconnectClient(clientId, reason);
+
///
/// Globally shuts down the library.
/// Disconnects clients if connected and stops server if running.
@@ -1206,10 +942,19 @@ namespace Unity.Netcode
if (IsServer || IsClient)
{
m_ShuttingDown = true;
- m_StopProcessingMessages = discardMessageQueue;
+ MessageManager.StopProcessing = discardMessageQueue;
}
- NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
+ NetworkConfig.NetworkTransport.OnTransportEvent -= ConnectionManager.HandleNetworkEvent;
+ }
+
+ // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
+ private void OnSceneUnloaded(Scene scene)
+ {
+ if (gameObject != null && scene == gameObject.scene)
+ {
+ OnDestroy();
+ }
}
internal void ShutdownInternal()
@@ -1219,1022 +964,85 @@ namespace Unity.Netcode
NetworkLog.LogInfo(nameof(ShutdownInternal));
}
- bool wasServer = IsServer;
- bool wasClient = IsClient;
- if (wasServer)
- {
- // make sure all messages are flushed before transport disconnect clients
- MessagingSystem?.ProcessSendQueues();
-
- var disconnectedIds = new HashSet();
-
- //Don't know if I have to disconnect the clients. I'm assuming the NetworkTransport does all the cleaning on shutdown. But this way the clients get a disconnect message from server (so long it does't get lost)
-
- foreach (KeyValuePair pair in ConnectedClients)
- {
- if (!disconnectedIds.Contains(pair.Key))
- {
- disconnectedIds.Add(pair.Key);
-
- if (pair.Key == NetworkConfig.NetworkTransport.ServerClientId)
- {
- continue;
- }
-
- DisconnectRemoteClient(pair.Key);
- }
- }
-
- foreach (KeyValuePair pair in PendingClients)
- {
- if (!disconnectedIds.Contains(pair.Key))
- {
- disconnectedIds.Add(pair.Key);
- if (pair.Key == NetworkConfig.NetworkTransport.ServerClientId)
- {
- continue;
- }
-
- DisconnectRemoteClient(pair.Key);
- }
- }
- }
-
- // Unregister network updates before trying to disconnect the client
this.UnregisterAllNetworkUpdates();
- if (IsClient && IsListening)
- {
- // Client only, send disconnect to server
- // If transport throws and exception, log the exception and
- // continue the shutdown sequence (or forever be shutting down)
- try
- {
- NetworkConfig.NetworkTransport.DisconnectLocalClient();
- }
- catch (Exception ex)
- {
- Debug.LogException(ex);
- }
- }
+ // Everything is shutdown in the order of their dependencies
+ DeferredMessageManager?.CleanupAllTriggers();
+ CustomMessagingManager = null;
- IsConnectedClient = false;
- IsApproved = false;
+ BehaviourUpdater?.Shutdown();
+ BehaviourUpdater = null;
+
+ // Shutdown connection manager last which shuts down transport
+ ConnectionManager.Shutdown();
+
+ if (MessageManager != null)
+ {
+ MessageManager.Dispose();
+ MessageManager = null;
+ }
// We need to clean up NetworkObjects before we reset the IsServer
// and IsClient properties. This provides consistency of these two
// property values for NetworkObjects that are still spawned when
// the shutdown cycle begins.
- if (SpawnManager != null)
- {
- SpawnManager.DespawnAndDestroyNetworkObjects();
- SpawnManager.ServerResetShudownStateForSceneObjects();
-
- SpawnManager = null;
- }
-
- IsServer = false;
- IsClient = false;
-
- if (NetworkTickSystem != null)
- {
- NetworkTickSystem.Tick -= OnNetworkManagerTick;
- NetworkTickSystem = null;
- }
-
- if (MessagingSystem != null)
- {
- MessagingSystem.Dispose();
- MessagingSystem = null;
- }
-
- if (NetworkConfig?.NetworkTransport != null)
- {
- NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
- }
-
- DeferredMessageManager?.CleanupAllTriggers();
-
- if (SceneManager != null)
- {
- // Let the NetworkSceneManager clean up its two SceneEvenData instances
- SceneManager.Dispose();
- SceneManager = null;
- }
-
- if (CustomMessagingManager != null)
- {
- CustomMessagingManager = null;
- }
-
- if (BehaviourUpdater != null)
- {
- BehaviourUpdater = null;
- }
-
- // This is required for handling the potential scenario where multiple NetworkManager instances are created.
- // See MTT-860 for more information
- if (IsListening)
- {
- //The Transport is set during initialization, thus it is possible for the Transport to be null
- NetworkConfig?.NetworkTransport?.Shutdown();
- }
-
- m_ClientIdToTransportIdMap.Clear();
- m_TransportIdToClientIdMap.Clear();
+ SpawnManager?.DespawnAndDestroyNetworkObjects();
+ SpawnManager?.ServerResetShudownStateForSceneObjects();
+ SpawnManager = null;
+ // Let the NetworkSceneManager clean up its two SceneEvenData instances
+ SceneManager?.Dispose();
+ SceneManager = null;
IsListening = false;
m_ShuttingDown = false;
- m_StopProcessingMessages = false;
- ClearClients();
+ if (ConnectionManager.LocalClient.IsClient)
+ {
+ // If we were a client, we want to know if we were a host
+ // client or not. (why we pass in "IsServer")
+ OnClientStopped?.Invoke(ConnectionManager.LocalClient.IsServer);
+ }
- if (wasClient)
+ if (ConnectionManager.LocalClient.IsServer)
{
- OnClientStopped?.Invoke(wasServer);
- }
- if (wasServer)
- {
- OnServerStopped?.Invoke(wasClient);
+ // If we were a server, we want to know if we were a host
+ // or not. (why we pass in "IsClient")
+ OnServerStopped?.Invoke(ConnectionManager.LocalClient.IsClient);
}
+ // Reset the client's roles
+ ConnectionManager.LocalClient.SetRole(false, false);
+
// This cleans up the internal prefabs list
- NetworkConfig?.Prefabs.Shutdown();
+ NetworkConfig?.Prefabs?.Shutdown();
// Reset the configuration hash for next session in the event
// that the prefab list changes
NetworkConfig?.ClearConfigHash();
+
+ // Time & tick systems should be the last system shutdown so other systems
+ // can unsubscribe from tick updates and such.
+ NetworkTimeSystem?.Shutdown();
+ NetworkTickSystem = null;
}
- ///
- public void NetworkUpdate(NetworkUpdateStage updateStage)
+ // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application.
+ private void OnApplicationQuit()
{
- switch (updateStage)
- {
- case NetworkUpdateStage.EarlyUpdate:
- OnNetworkEarlyUpdate();
- break;
- case NetworkUpdateStage.PreUpdate:
- OnNetworkPreUpdate();
- break;
- case NetworkUpdateStage.PostLateUpdate:
- OnNetworkPostLateUpdate();
- break;
- }
+ OnDestroy();
}
- private void ProcessPendingApprovals()
+ // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
+ private void OnDestroy()
{
- List senders = null;
+ ShutdownInternal();
- foreach (var responsePair in ClientsToApprove)
+ UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
+
+ if (Singleton == this)
{
- var response = responsePair.Value;
- var senderId = responsePair.Key;
-
- if (!response.Pending)
- {
- try
- {
- HandleConnectionApproval(senderId, response);
-
- if (senders == null)
- {
- senders = new List();
- }
- senders.Add(senderId);
- }
- catch (Exception e)
- {
- Debug.LogException(e);
- }
- }
+ Singleton = null;
}
-
- if (senders != null)
- {
- foreach (var sender in senders)
- {
- ClientsToApprove.Remove(sender);
- }
- }
- }
-
- private void OnNetworkEarlyUpdate()
- {
- if (!IsListening)
- {
- return;
- }
-
- ProcessPendingApprovals();
-
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_TransportPoll.Begin();
-#endif
- NetworkEvent networkEvent;
- do
- {
- networkEvent = NetworkConfig.NetworkTransport.PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime);
- HandleRawTransportPoll(networkEvent, clientId, payload, receiveTime);
- // Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum)
- } while (IsListening && networkEvent != NetworkEvent.Nothing);
-
- MessagingSystem.ProcessIncomingMessageQueue();
- MessagingSystem.CleanupDisconnectedClients();
-
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_TransportPoll.End();
-#endif
- }
-
- // TODO Once we have a way to subscribe to NetworkUpdateLoop with order we can move this out of NetworkManager but for now this needs to be here because we need strict ordering.
- private void OnNetworkPreUpdate()
- {
- if (IsServer == false && IsConnectedClient == false)
- {
- // As a client wait to run the time system until we are connected.
- return;
- }
-
- if (m_ShuttingDown && m_StopProcessingMessages)
- {
- return;
- }
-
- // Only update RTT here, server time is updated by time sync messages
- var reset = NetworkTimeSystem.Advance(RealTimeProvider.UnscaledDeltaTime);
- if (reset)
- {
- NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
- }
- NetworkTickSystem.UpdateTick(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
-
- if (IsServer == false)
- {
- NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + RealTimeProvider.UnscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
- }
- }
-
- private void OnNetworkPostLateUpdate()
- {
-
- if (!m_ShuttingDown || !m_StopProcessingMessages)
- {
- // This should be invoked just prior to the MessagingSystem
- // processes its outbound queue.
- SceneManager.CheckForAndSendNetworkObjectSceneChanged();
-
- MessagingSystem.ProcessSendQueues();
- NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count);
- NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1);
- NetworkMetrics.DispatchFrame();
-
- NetworkObject.VerifyParentingStatus();
- }
- DeferredMessageManager.CleanupStaleTriggers();
-
- if (m_ShuttingDown)
- {
- ShutdownInternal();
- }
- }
-
- ///
- /// This function runs once whenever the local tick is incremented and is responsible for the following (in order):
- /// - collect commands/inputs and send them to the server (TBD)
- /// - call NetworkFixedUpdate on all NetworkBehaviours in prediction/client authority mode
- ///
- private void OnNetworkManagerTick()
- {
- // Do NetworkVariable updates
- BehaviourUpdater.NetworkBehaviourUpdate(this);
-
- // Handle NetworkObjects to show
- foreach (var client in ObjectsToShowToClient)
- {
- ulong clientId = client.Key;
- foreach (var networkObject in client.Value)
- {
- SpawnManager.SendSpawnCallForObject(clientId, networkObject);
- }
- }
- ObjectsToShowToClient.Clear();
-
- int timeSyncFrequencyTicks = (int)(k_TimeSyncFrequency * NetworkConfig.TickRate);
- if (IsServer && NetworkTickSystem.ServerTime.Tick % timeSyncFrequencyTicks == 0)
- {
- SyncTime();
- }
- }
-
- private void SendConnectionRequest()
- {
- var message = new ConnectionRequestMessage
- {
- // Since only a remote client will send a connection request,
- // we should always force the rebuilding of the NetworkConfig hash value
- ConfigHash = NetworkConfig.GetConfig(false),
- ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
- ConnectionData = NetworkConfig.ConnectionData,
- MessageVersions = new NativeArray(MessagingSystem.MessageHandlers.Length, Allocator.Temp)
- };
- for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
- {
- if (MessagingSystem.MessageTypes[index] != null)
- {
- var type = MessagingSystem.MessageTypes[index];
- message.MessageVersions[index] = new MessageVersionData
- {
- Hash = XXHash.Hash32(type.FullName),
- Version = MessagingSystem.GetLocalVersion(type)
- };
- }
- }
-
- SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId);
- message.MessageVersions.Dispose();
- }
-
- private IEnumerator ApprovalTimeout(ulong clientId)
- {
- var timeStarted = IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup;
- var timedOut = false;
- var connectionApproved = false;
- var connectionNotApproved = false;
- var timeoutMarker = timeStarted + NetworkConfig.ClientConnectionBufferTimeout;
-
- while (IsListening && !ShutdownInProgress && !timedOut && !connectionApproved)
- {
- yield return null;
- // Check if we timed out
- timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup);
-
- if (IsServer)
- {
- // When the client is no longer in the pending clients list and is in the connected clients list
- // it has been approved
- connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId);
-
- // For the server side, if the client is in neither list then it was declined or the client disconnected
- connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId);
- }
- else
- {
- connectionApproved = IsApproved;
- }
- }
-
- // Exit coroutine if we are no longer listening or a shutdown is in progress (client or server)
- if (!IsListening || ShutdownInProgress)
- {
- yield break;
- }
-
- // If the client timed out or was not approved
- if (timedOut || connectionNotApproved)
- {
- // Timeout
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- if (timedOut)
- {
- if (IsServer)
- {
- // Log a warning that the transport detected a connection but then did not receive a follow up connection request message.
- // (hacking or something happened to the server's network connection)
- NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message.");
- }
- else
- {
- // We only provide informational logging for the client side
- NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request.");
- }
- }
- else if (connectionNotApproved)
- {
- NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved.");
- }
- }
-
- if (IsServer)
- {
- DisconnectClient(clientId);
- }
- else
- {
- Shutdown(true);
- }
- }
- }
-
- internal ulong TransportIdToClientId(ulong transportId)
- {
- return transportId == m_ServerTransportId ? ServerClientId : m_TransportIdToClientIdMap[transportId];
- }
-
- internal ulong ClientIdToTransportId(ulong clientId)
- {
- return clientId == ServerClientId ? m_ServerTransportId : m_ClientIdToTransportIdMap[clientId];
- }
-
- private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, ArraySegment payload, float receiveTime)
- {
- var transportId = clientId;
- switch (networkEvent)
- {
- case NetworkEvent.Connect:
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_TransportConnect.Begin();
-#endif
-
- // Assumptions:
- // - When server receives a connection, it *must be* a client
- // - When client receives one, it *must be* the server
- // Client's can't connect to or talk to other clients.
- // Server is a sentinel so only one exists, if we are server, we can't be
- // connecting to it.
- if (IsServer)
- {
- clientId = m_NextClientId++;
- }
- else
- {
- clientId = ServerClientId;
- }
- m_ClientIdToTransportIdMap[clientId] = transportId;
- m_TransportIdToClientIdMap[transportId] = clientId;
-
- MessagingSystem.ClientConnected(clientId);
- if (IsServer)
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo("Client Connected");
- }
-
- PendingClients.Add(clientId, new PendingClient()
- {
- ClientId = clientId,
- ConnectionState = PendingClient.State.PendingConnection
- });
-
- StartCoroutine(ApprovalTimeout(clientId));
- }
- else
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo("Connected");
- }
-
- SendConnectionRequest();
- StartCoroutine(ApprovalTimeout(clientId));
- }
-
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_TransportConnect.End();
-#endif
- break;
- case NetworkEvent.Data:
- {
- clientId = TransportIdToClientId(clientId);
-
- HandleIncomingData(clientId, payload, receiveTime);
- break;
- }
- case NetworkEvent.Disconnect:
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_TransportDisconnect.Begin();
-#endif
- clientId = TransportIdCleanUp(clientId, transportId);
-
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo($"Disconnect Event From {clientId}");
- }
-
- // Process the incoming message queue so that we get everything from the server disconnecting us
- // or, if we are the server, so we got everything from that client.
- MessagingSystem.ProcessIncomingMessageQueue();
-
- try
- {
- OnClientDisconnectCallback?.Invoke(clientId);
- }
- catch (Exception exception)
- {
- Debug.LogException(exception);
- }
-
- if (IsServer)
- {
- OnClientDisconnectFromServer(clientId);
- }
- else
- {
- // We must pass true here and not process any sends messages
- // as we are no longer connected and thus there is no one to
- // send any messages to and this will cause an exception within
- // UnityTransport as the client ID is no longer valid.
- Shutdown(true);
- }
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_TransportDisconnect.End();
-#endif
- break;
-
- case NetworkEvent.TransportFailure:
- Debug.LogError($"Shutting down due to network transport failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
- OnTransportFailure?.Invoke();
- Shutdown(true);
- break;
- }
- }
-
- ///
- /// Handles cleaning up the transport id/client id tables after
- /// receiving a disconnect event from transport
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private ulong TransportIdCleanUp(ulong clientId, ulong transportId)
- {
- // This check is for clients that attempted to connect but failed.
- // When this happens, the client will not have an entry within the
- // m_TransportIdToClientIdMap or m_ClientIdToTransportIdMap lookup
- // tables so we exit early and just return 0 to be used for the
- // disconnect event.
- if (!IsServer && !m_TransportIdToClientIdMap.ContainsKey(clientId))
- {
- return 0;
- }
-
- clientId = TransportIdToClientId(clientId);
-
- m_TransportIdToClientIdMap.Remove(transportId);
- m_ClientIdToTransportIdMap.Remove(clientId);
-
- return clientId;
- }
-
- internal unsafe int SendMessage(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
- where TMessageType : INetworkMessage
- where TClientIdListType : IReadOnlyList
- {
- // Prevent server sending to itself
- if (IsServer)
- {
- ulong* nonServerIds = stackalloc ulong[clientIds.Count];
- int newIdx = 0;
- for (int idx = 0; idx < clientIds.Count; ++idx)
- {
- if (clientIds[idx] == ServerClientId)
- {
- continue;
- }
-
- nonServerIds[newIdx++] = clientIds[idx];
- }
-
- if (newIdx == 0)
- {
- return 0;
- }
- return MessagingSystem.SendMessage(ref message, delivery, nonServerIds, newIdx);
- }
- // else
- if (clientIds.Count != 1 || clientIds[0] != ServerClientId)
- {
- throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}");
- }
-
- return MessagingSystem.SendMessage(ref message, delivery, clientIds);
- }
-
- internal unsafe int SendMessage(ref T message, NetworkDelivery delivery,
- ulong* clientIds, int numClientIds)
- where T : INetworkMessage
- {
- // Prevent server sending to itself
- if (IsServer)
- {
- ulong* nonServerIds = stackalloc ulong[numClientIds];
- int newIdx = 0;
- for (int idx = 0; idx < numClientIds; ++idx)
- {
- if (clientIds[idx] == ServerClientId)
- {
- continue;
- }
-
- nonServerIds[newIdx++] = clientIds[idx];
- }
-
- if (newIdx == 0)
- {
- return 0;
- }
- return MessagingSystem.SendMessage(ref message, delivery, nonServerIds, newIdx);
- }
- // else
- if (numClientIds != 1 || clientIds[0] != ServerClientId)
- {
- throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}");
- }
-
- return MessagingSystem.SendMessage(ref message, delivery, clientIds, numClientIds);
- }
-
- internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in NativeArray clientIds)
- where T : INetworkMessage
- {
- return SendMessage(ref message, delivery, (ulong*)clientIds.GetUnsafePtr(), clientIds.Length);
- }
-
- internal int SendMessage(ref T message, NetworkDelivery delivery, ulong clientId)
- where T : INetworkMessage
- {
- // Prevent server sending to itself
- if (IsServer && clientId == ServerClientId)
- {
- return 0;
- }
-
- if (!IsServer && clientId != ServerClientId)
- {
- throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}");
- }
- return MessagingSystem.SendMessage(ref message, delivery, clientId);
- }
-
- internal int SendPreSerializedMessage(in FastBufferWriter writer, int maxSize, ref T message, NetworkDelivery delivery, ulong clientId)
- where T : INetworkMessage
- {
- return MessagingSystem.SendPreSerializedMessage(writer, maxSize, ref message, delivery, clientId);
- }
-
- internal void HandleIncomingData(ulong clientId, ArraySegment payload, float receiveTime)
- {
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_HandleIncomingData.Begin();
-#endif
-
- MessagingSystem.HandleIncomingData(clientId, payload, receiveTime);
-
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_HandleIncomingData.End();
-#endif
- }
-
- ///
- /// Disconnects the remote client.
- ///
- /// The ClientId to disconnect
- public void DisconnectClient(ulong clientId)
- {
- DisconnectClient(clientId, null);
- }
-
- ///
- /// Disconnects the remote client.
- ///
- /// The ClientId to disconnect
- /// Disconnection reason. If set, client will receive a DisconnectReasonMessage and have the
- /// reason available in the NetworkManager.DisconnectReason property
- public void DisconnectClient(ulong clientId, string reason)
- {
- if (!IsServer)
- {
- throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
- }
-
- if (!string.IsNullOrEmpty(reason))
- {
- var disconnectReason = new DisconnectReasonMessage
- {
- Reason = reason
- };
- SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
- }
- MessagingSystem.ProcessSendQueues();
-
- OnClientDisconnectFromServer(clientId);
- DisconnectRemoteClient(clientId);
- }
-
- private void OnClientDisconnectFromServer(ulong clientId)
- {
- PendingClients.Remove(clientId);
-
- if (ConnectedClients.TryGetValue(clientId, out NetworkClient networkClient))
- {
- if (IsServer)
- {
- var playerObject = networkClient.PlayerObject;
- if (playerObject != null)
- {
- if (!playerObject.DontDestroyWithOwner)
- {
- if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
- {
- PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
- }
- else
- {
- // Call despawn to assure NetworkBehaviour.OnNetworkDespawn is invoked
- // on the server-side (when the client side disconnected).
- // This prevents the issue (when just destroying the GameObject) where
- // any NetworkBehaviour component(s) destroyed before the NetworkObject
- // would not have OnNetworkDespawn invoked.
- SpawnManager.DespawnObject(playerObject, true);
- }
- }
- else
- {
- playerObject.RemoveOwnership();
- }
- }
-
- // Get the NetworkObjects owned by the disconnected client
- var clientOwnedObjects = SpawnManager.GetClientOwnedObjects(clientId);
- if (clientOwnedObjects == null)
- {
- // This could happen if a client is never assigned a player object and is disconnected
- // Only log this in verbose/developer mode
- if (LogLevel == LogLevel.Developer)
- {
- NetworkLog.LogWarning($"ClientID {clientId} disconnected with (0) zero owned objects! Was a player prefab not assigned?");
- }
- }
- else
- {
- // Handle changing ownership and prefab handlers
- for (int i = clientOwnedObjects.Count - 1; i >= 0; i--)
- {
- var ownedObject = clientOwnedObjects[i];
- if (ownedObject != null)
- {
- if (!ownedObject.DontDestroyWithOwner)
- {
- if (PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash))
- {
- PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]);
- }
- else
- {
- Destroy(ownedObject.gameObject);
- }
- }
- else
- {
- ownedObject.RemoveOwnership();
- }
- }
- }
- }
-
- // TODO: Could(should?) be replaced with more memory per client, by storing the visibility
- foreach (var sobj in SpawnManager.SpawnedObjectsList)
- {
- sobj.Observers.Remove(clientId);
- }
- }
-
- for (int i = 0; i < ConnectedClientsList.Count; i++)
- {
- if (ConnectedClientsList[i].ClientId == clientId)
- {
- m_ConnectedClientsList.RemoveAt(i);
- break;
- }
- }
-
- for (int i = 0; i < ConnectedClientsIds.Count; i++)
- {
- if (ConnectedClientsIds[i] == clientId)
- {
- m_ConnectedClientIds.RemoveAt(i);
- break;
- }
- }
-
- m_ConnectedClients.Remove(clientId);
- }
- MessagingSystem.ClientDisconnected(clientId);
- }
-
- private void SyncTime()
- {
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_SyncTime.Begin();
-#endif
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo("Syncing Time To Clients");
- }
-
- var message = new TimeSyncMessage
- {
- Tick = NetworkTickSystem.ServerTime.Tick
- };
- SendMessage(ref message, NetworkDelivery.Unreliable, ConnectedClientsIds);
-#if DEVELOPMENT_BUILD || UNITY_EDITOR
- s_SyncTime.End();
-#endif
- }
-
- ///
- /// Server Side: Handles the approval of a client
- ///
- /// The Network Id of the client being approved
- /// The response to allow the player in or not, with its parameters
- internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalResponse response)
- {
- if (response.Approved)
- {
- // Inform new client it got approved
- PendingClients.Remove(ownerClientId);
-
- var client = new NetworkClient { ClientId = ownerClientId, };
- m_ConnectedClients.Add(ownerClientId, client);
- m_ConnectedClientsList.Add(client);
- m_ConnectedClientIds.Add(client.ClientId);
-
- if (response.CreatePlayerObject)
- {
- var prefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent();
- var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash;
-
- // Generate a SceneObject for the player object to spawn
- // Note: This is only to create the local NetworkObject,
- // many of the serialized properties of the player prefab
- // will be set when instantiated.
- var sceneObject = new NetworkObject.SceneObject
- {
- OwnerClientId = ownerClientId,
- IsPlayerObject = true,
- IsSceneObject = false,
- HasTransform = prefabNetworkObject.SynchronizeTransform,
- Hash = playerPrefabHash,
- TargetClientId = ownerClientId,
- Transform = new NetworkObject.SceneObject.TransformData
- {
- Position = response.Position.GetValueOrDefault(),
- Rotation = response.Rotation.GetValueOrDefault()
- }
- };
-
- // Create the player NetworkObject locally
- var networkObject = SpawnManager.CreateLocalNetworkObject(sceneObject);
-
- // Spawn the player NetworkObject locally
- SpawnManager.SpawnNetworkObjectLocally(
- networkObject,
- SpawnManager.GetNetworkObjectId(),
- sceneObject: false,
- playerObject: true,
- ownerClientId,
- destroyWithScene: false);
-
- ConnectedClients[ownerClientId].PlayerObject = networkObject;
- }
-
- // Server doesn't send itself the connection approved message
- if (ownerClientId != ServerClientId)
- {
- var message = new ConnectionApprovedMessage
- {
- OwnerClientId = ownerClientId,
- NetworkTick = LocalTime.Tick
- };
- if (!NetworkConfig.EnableSceneManagement)
- {
- if (SpawnManager.SpawnedObjectsList.Count != 0)
- {
- message.SpawnedObjectsList = SpawnManager.SpawnedObjectsList;
- }
- }
-
- message.MessageVersions = new NativeArray(MessagingSystem.MessageHandlers.Length, Allocator.Temp);
- for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
- {
- if (MessagingSystem.MessageTypes[index] != null)
- {
- var type = MessagingSystem.MessageTypes[index];
- message.MessageVersions[index] = new MessageVersionData
- {
- Hash = XXHash.Hash32(type.FullName),
- Version = MessagingSystem.GetLocalVersion(type)
- };
- }
- }
-
- SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
- message.MessageVersions.Dispose();
-
- // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
- if (!NetworkConfig.EnableSceneManagement)
- {
- InvokeOnClientConnectedCallback(ownerClientId);
- }
- else
- {
- SceneManager.SynchronizeNetworkObjects(ownerClientId);
- }
- }
- else // Server just adds itself as an observer to all spawned NetworkObjects
- {
- LocalClient = client;
- SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
- }
-
- if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
- {
- return;
- }
-
- // Separating this into a contained function call for potential further future separation of when this notification is sent.
- ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash);
- }
- else
- {
- if (!string.IsNullOrEmpty(response.Reason))
- {
- var disconnectReason = new DisconnectReasonMessage
- {
- Reason = response.Reason
- };
- SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
-
- MessagingSystem.ProcessSendQueues();
- }
-
- PendingClients.Remove(ownerClientId);
- DisconnectRemoteClient(ownerClientId);
- }
- }
-
- ///
- /// Spawns the newly approved player
- ///
- /// new player client identifier
- /// the prefab GlobalObjectIdHash value for this player
- internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash)
- {
- foreach (var clientPair in ConnectedClients)
- {
- if (clientPair.Key == clientId ||
- clientPair.Key == ServerClientId || // Server already spawned it
- ConnectedClients[clientId].PlayerObject == null ||
- !ConnectedClients[clientId].PlayerObject.Observers.Contains(clientPair.Key))
- {
- continue; //The new client.
- }
-
- var message = new CreateObjectMessage
- {
- ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key)
- };
- message.ObjectInfo.Hash = playerPrefabHash;
- message.ObjectInfo.IsSceneObject = false;
- message.ObjectInfo.HasParent = false;
- message.ObjectInfo.IsPlayerObject = true;
- message.ObjectInfo.OwnerClientId = clientId;
- var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
- NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
- }
- }
-
- internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId)
- {
- if (!ObjectsToShowToClient.ContainsKey(clientId))
- {
- ObjectsToShowToClient.Add(clientId, new List());
- }
- ObjectsToShowToClient[clientId].Add(networkObject);
- }
-
- // returns whether any matching objects would have become visible and were returned to hidden state
- internal bool RemoveObjectFromShowingTo(NetworkObject networkObject, ulong clientId)
- {
- var ret = false;
- if (!ObjectsToShowToClient.ContainsKey(clientId))
- {
- return false;
- }
-
- // probably overkill, but deals with multiple entries
- while (ObjectsToShowToClient[clientId].Contains(networkObject))
- {
- Debug.LogWarning(
- "Object was shown and hidden from the same client in the same Network frame. As a result, the client will _not_ receive a NetworkSpawn");
- ObjectsToShowToClient[clientId].Remove(networkObject);
- ret = true;
- }
-
- networkObject.Observers.Remove(clientId);
-
- return ret;
}
}
}
diff --git a/Runtime/Core/NetworkObject.cs b/Runtime/Core/NetworkObject.cs
index 74a8080..3643d44 100644
--- a/Runtime/Core/NetworkObject.cs
+++ b/Runtime/Core/NetworkObject.cs
@@ -188,6 +188,12 @@ namespace Unity.Netcode
///
public Action OnMigratedToNewScene;
+ ///
+ /// When set to false, the NetworkObject will be spawned with no observers initially (other than the server)
+ ///
+ [Tooltip("When false, the NetworkObject will spawn with no observers initially. (default is true)")]
+ public bool SpawnWithObservers = true;
+
///
/// Delegate type for checking visibility
///
@@ -361,8 +367,7 @@ namespace Unity.Netcode
}
return;
}
-
- NetworkManager.MarkObjectForShowingTo(this, clientId);
+ NetworkManager.SpawnManager.MarkObjectForShowingTo(this, clientId);
Observers.Add(clientId);
}
@@ -452,7 +457,7 @@ namespace Unity.Netcode
throw new VisibilityChangeException("Cannot hide an object from the server");
}
- if (!NetworkManager.RemoveObjectFromShowingTo(this, clientId))
+ if (!NetworkManager.SpawnManager.RemoveObjectFromShowingTo(this, clientId))
{
if (!Observers.Contains(clientId))
{
@@ -466,7 +471,7 @@ namespace Unity.Netcode
DestroyGameObject = !IsSceneObject.Value
};
// Send destroy call
- var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
+ var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
}
}
@@ -532,14 +537,30 @@ namespace Unity.Netcode
private void OnDestroy()
{
- if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned &&
- (IsSceneObject == null || (IsSceneObject.Value != true)))
+ // If no NetworkManager is assigned, then just exit early
+ if (!NetworkManager)
{
- throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
+ return;
}
- if (NetworkManager != null && NetworkManager.SpawnManager != null &&
- NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
+ if (NetworkManager.IsListening && !NetworkManager.IsServer && IsSpawned &&
+ (IsSceneObject == null || (IsSceneObject.Value != true)))
+ {
+ // Clients should not despawn NetworkObjects while connected to a session, but we don't want to destroy the current call stack
+ // if this happens. Instead, we should just generate a network log error and exit early (as long as we are not shutting down).
+ if (!NetworkManager.ShutdownInProgress)
+ {
+ // Since we still have a session connection, log locally and on the server to inform user of this issue.
+ if (NetworkManager.LogLevel <= LogLevel.Error)
+ {
+ NetworkLog.LogErrorServer($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
+ }
+ return;
+ }
+ // Otherwise, clients can despawn NetworkObjects while shutting down and should not generate any messages when this happens
+ }
+
+ if (NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
if (this == networkObject)
{
@@ -898,7 +919,7 @@ namespace Unity.Netcode
}
}
- NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx);
+ NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx);
}
}
diff --git a/Runtime/Logging/NetworkLog.cs b/Runtime/Logging/NetworkLog.cs
index b5c210b..c8160f0 100644
--- a/Runtime/Logging/NetworkLog.cs
+++ b/Runtime/Logging/NetworkLog.cs
@@ -51,35 +51,58 @@ namespace Unity.Netcode
/// The message to log
public static void LogErrorServer(string message) => LogServer(message, LogType.Error);
+ internal static NetworkManager NetworkManagerOverride;
+
private static void LogServer(string message, LogType logType)
{
+ var networkManager = NetworkManagerOverride ??= NetworkManager.Singleton;
// Get the sender of the local log
- ulong localId = NetworkManager.Singleton != null ? NetworkManager.Singleton.LocalClientId : 0;
-
+ ulong localId = networkManager?.LocalClientId ?? 0;
+ bool isServer = networkManager?.IsServer ?? true;
switch (logType)
{
case LogType.Info:
- LogInfoServerLocal(message, localId);
+ if (isServer)
+ {
+ LogInfoServerLocal(message, localId);
+ }
+ else
+ {
+ LogInfo(message);
+ }
break;
case LogType.Warning:
- LogWarningServerLocal(message, localId);
+ if (isServer)
+ {
+ LogWarningServerLocal(message, localId);
+ }
+ else
+ {
+ LogWarning(message);
+ }
break;
case LogType.Error:
- LogErrorServerLocal(message, localId);
+ if (isServer)
+ {
+ LogErrorServerLocal(message, localId);
+ }
+ else
+ {
+ LogError(message);
+ }
break;
}
- if (NetworkManager.Singleton != null && !NetworkManager.Singleton.IsServer && NetworkManager.Singleton.NetworkConfig.EnableNetworkLogs)
+ if (!isServer && networkManager.NetworkConfig.EnableNetworkLogs)
{
-
var networkMessage = new ServerLogMessage
{
LogType = logType,
Message = message
};
- var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId);
+ var size = networkManager.ConnectionManager.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId);
- NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size);
+ networkManager.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size);
}
}
diff --git a/Runtime/Messaging/CustomMessageManager.cs b/Runtime/Messaging/CustomMessageManager.cs
index c1140da..4e15ec6 100644
--- a/Runtime/Messaging/CustomMessageManager.cs
+++ b/Runtime/Messaging/CustomMessageManager.cs
@@ -90,7 +90,7 @@ namespace Unity.Netcode
{
SendData = messageBuffer
};
- var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds);
+ var size = m_NetworkManager.ConnectionManager.SendMessage(ref message, networkDelivery, clientIds);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
@@ -123,7 +123,7 @@ namespace Unity.Netcode
{
SendData = messageBuffer
};
- var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId);
+ var size = m_NetworkManager.ConnectionManager.SendMessage(ref message, networkDelivery, clientId);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
{
@@ -275,7 +275,7 @@ namespace Unity.Netcode
Hash = hash,
SendData = messageStream,
};
- var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId);
+ var size = m_NetworkManager.ConnectionManager.SendMessage(ref message, networkDelivery, clientId);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
@@ -333,7 +333,7 @@ namespace Unity.Netcode
Hash = hash,
SendData = messageStream
};
- var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds);
+ var size = m_NetworkManager.ConnectionManager.SendMessage(ref message, networkDelivery, clientIds);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
diff --git a/Runtime/Messaging/DefaultMessageSender.cs b/Runtime/Messaging/DefaultMessageSender.cs
new file mode 100644
index 0000000..021ad0c
--- /dev/null
+++ b/Runtime/Messaging/DefaultMessageSender.cs
@@ -0,0 +1,26 @@
+
+namespace Unity.Netcode
+{
+ ///
+ /// Default NetworkTransport Message Sender
+ ///
+ ///
+ internal class DefaultMessageSender : INetworkMessageSender
+ {
+ private NetworkTransport m_NetworkTransport;
+ private NetworkConnectionManager m_ConnectionManager;
+
+ public DefaultMessageSender(NetworkManager manager)
+ {
+ m_NetworkTransport = manager.NetworkConfig.NetworkTransport;
+ m_ConnectionManager = manager.ConnectionManager;
+ }
+
+ public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
+ {
+ var sendBuffer = batchData.ToTempByteArray();
+
+ m_NetworkTransport.Send(m_ConnectionManager.ClientIdToTransportId(clientId), sendBuffer, delivery);
+ }
+ }
+}
diff --git a/Runtime/Messaging/DefaultMessageSender.cs.meta b/Runtime/Messaging/DefaultMessageSender.cs.meta
new file mode 100644
index 0000000..f92a51c
--- /dev/null
+++ b/Runtime/Messaging/DefaultMessageSender.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5ea6bdd38832d9947bb21c4b35bf61d0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Messaging/DeferredMessageManager.cs b/Runtime/Messaging/DeferredMessageManager.cs
index 8c48909..227b167 100644
--- a/Runtime/Messaging/DeferredMessageManager.cs
+++ b/Runtime/Messaging/DeferredMessageManager.cs
@@ -3,12 +3,12 @@ using Unity.Collections;
namespace Unity.Netcode
{
- internal class DeferredMessageManager : IDeferredMessageManager
+ internal class DeferredMessageManager : IDeferredNetworkMessageManager
{
protected struct TriggerData
{
public FastBufferReader Reader;
- public MessageHeader Header;
+ public NetworkMessageHeader Header;
public ulong SenderId;
public float Timestamp;
public int SerializedHeaderSize;
@@ -19,7 +19,7 @@ namespace Unity.Netcode
public NativeList TriggerData;
}
- protected readonly Dictionary> m_Triggers = new Dictionary>();
+ protected readonly Dictionary> m_Triggers = new Dictionary>();
private readonly NetworkManager m_NetworkManager;
@@ -36,7 +36,7 @@ namespace Unity.Netcode
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
///
- public virtual unsafe void DeferMessage(IDeferredMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context)
+ public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context)
{
if (!m_Triggers.TryGetValue(trigger, out var triggers))
{
@@ -90,7 +90,7 @@ namespace Unity.Netcode
}
}
- protected virtual void PurgeTrigger(IDeferredMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo)
+ protected virtual void PurgeTrigger(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
@@ -105,7 +105,7 @@ namespace Unity.Netcode
triggerInfo.TriggerData.Dispose();
}
- public virtual void ProcessTriggers(IDeferredMessageManager.TriggerType trigger, ulong key)
+ public virtual void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key)
{
if (m_Triggers.TryGetValue(trigger, out var triggers))
{
@@ -116,7 +116,7 @@ namespace Unity.Netcode
foreach (var deferredMessage in triggerInfo.TriggerData)
{
// Reader will be disposed within HandleMessage
- m_NetworkManager.MessagingSystem.HandleMessage(deferredMessage.Header, deferredMessage.Reader, deferredMessage.SenderId, deferredMessage.Timestamp, deferredMessage.SerializedHeaderSize);
+ m_NetworkManager.ConnectionManager.MessageManager.HandleMessage(deferredMessage.Header, deferredMessage.Reader, deferredMessage.SenderId, deferredMessage.Timestamp, deferredMessage.SerializedHeaderSize);
}
triggerInfo.TriggerData.Dispose();
diff --git a/Runtime/Messaging/DisconnectReasonMessage.cs b/Runtime/Messaging/DisconnectReasonMessage.cs
index 7ea6284..b26982e 100644
--- a/Runtime/Messaging/DisconnectReasonMessage.cs
+++ b/Runtime/Messaging/DisconnectReasonMessage.cs
@@ -10,11 +10,9 @@ namespace Unity.Netcode
{
string reasonSent = Reason ?? string.Empty;
- // Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message
- // itself. However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion
- // on this side of things - we just have to make sure the receiving side knows what version we sent it,
- // since whoever has the higher version number is responsible for versioning and they may be the one
- // with the higher version number.
+ // Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message itself.
+ // However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion on this side of things.
+ // We just have to make sure the receiving side knows what version we sent it, since whoever has the higher version number is responsible for versioning and they may be the one with the higher version number.
BytePacker.WriteValueBitPacked(writer, Version);
if (writer.TryBeginWrite(FastBufferWriter.GetWriteSize(reasonSent)))
@@ -24,15 +22,14 @@ namespace Unity.Netcode
else
{
writer.WriteValueSafe(string.Empty);
- NetworkLog.LogWarning(
- "Disconnect reason didn't fit. Disconnected without sending a reason. Consider shortening the reason string.");
+ NetworkLog.LogWarning("Disconnect reason didn't fit. Disconnected without sending a reason. Consider shortening the reason string.");
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
- // Since we don't get a ConnectionApprovedMessage, the version for this message is encded with the message
- // itself. This will override what we got from MessagingSystem... which will always be 0 here.
+ // Since we don't get a ConnectionApprovedMessage, the version for this message is encded with the message itself.
+ // This will override what we got from MessageManager... which will always be 0 here.
ByteUnpacker.ReadValueBitPacked(reader, out receivedMessageVersion);
reader.ReadValueSafe(out Reason);
return true;
@@ -40,7 +37,7 @@ namespace Unity.Netcode
public void Handle(ref NetworkContext context)
{
- ((NetworkManager)context.SystemOwner).DisconnectReason = Reason;
+ ((NetworkManager)context.SystemOwner).ConnectionManager.DisconnectReason = Reason;
}
};
}
diff --git a/Runtime/Messaging/IDeferredMessageManager.cs b/Runtime/Messaging/IDeferredNetworkMessageManager.cs
similarity index 96%
rename from Runtime/Messaging/IDeferredMessageManager.cs
rename to Runtime/Messaging/IDeferredNetworkMessageManager.cs
index d1f35c3..1fc7a4b 100644
--- a/Runtime/Messaging/IDeferredMessageManager.cs
+++ b/Runtime/Messaging/IDeferredNetworkMessageManager.cs
@@ -1,6 +1,6 @@
namespace Unity.Netcode
{
- internal interface IDeferredMessageManager
+ internal interface IDeferredNetworkMessageManager
{
internal enum TriggerType
{
diff --git a/Runtime/Messaging/IDeferredMessageManager.cs.meta b/Runtime/Messaging/IDeferredNetworkMessageManager.cs.meta
similarity index 100%
rename from Runtime/Messaging/IDeferredMessageManager.cs.meta
rename to Runtime/Messaging/IDeferredNetworkMessageManager.cs.meta
diff --git a/Runtime/Messaging/ILPPMessageProvider.cs b/Runtime/Messaging/ILPPMessageProvider.cs
index b228b20..620ad97 100644
--- a/Runtime/Messaging/ILPPMessageProvider.cs
+++ b/Runtime/Messaging/ILPPMessageProvider.cs
@@ -2,14 +2,14 @@ using System.Collections.Generic;
namespace Unity.Netcode
{
- internal struct ILPPMessageProvider : IMessageProvider
+ internal struct ILPPMessageProvider : INetworkMessageProvider
{
#pragma warning disable IDE1006 // disable naming rule violation check
// This is NOT modified by RuntimeAccessModifiersILPP right now, but is populated by ILPP.
- internal static readonly List __network_message_types = new List();
+ internal static readonly List __network_message_types = new List();
#pragma warning restore IDE1006 // restore naming rule violation check
- public List GetMessages()
+ public List GetMessages()
{
return __network_message_types;
}
diff --git a/Runtime/Messaging/IMessageProvider.cs b/Runtime/Messaging/IMessageProvider.cs
deleted file mode 100644
index d974094..0000000
--- a/Runtime/Messaging/IMessageProvider.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Collections.Generic;
-
-namespace Unity.Netcode
-{
- internal interface IMessageProvider
- {
- List GetMessages();
- }
-}
diff --git a/Runtime/Messaging/INetworkMessage.cs b/Runtime/Messaging/INetworkMessage.cs
index c05587a..46267d5 100644
--- a/Runtime/Messaging/INetworkMessage.cs
+++ b/Runtime/Messaging/INetworkMessage.cs
@@ -1,4 +1,3 @@
-using Unity.Collections;
namespace Unity.Netcode
{
diff --git a/Runtime/Messaging/INetworkMessageProvider.cs b/Runtime/Messaging/INetworkMessageProvider.cs
new file mode 100644
index 0000000..dcd0c1b
--- /dev/null
+++ b/Runtime/Messaging/INetworkMessageProvider.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Unity.Netcode
+{
+ internal interface INetworkMessageProvider
+ {
+ List GetMessages();
+ }
+}
diff --git a/Runtime/Messaging/IMessageProvider.cs.meta b/Runtime/Messaging/INetworkMessageProvider.cs.meta
similarity index 100%
rename from Runtime/Messaging/IMessageProvider.cs.meta
rename to Runtime/Messaging/INetworkMessageProvider.cs.meta
diff --git a/Runtime/Messaging/IMessageSender.cs b/Runtime/Messaging/INetworkMessageSender.cs
similarity index 74%
rename from Runtime/Messaging/IMessageSender.cs
rename to Runtime/Messaging/INetworkMessageSender.cs
index 5248503..5f20746 100644
--- a/Runtime/Messaging/IMessageSender.cs
+++ b/Runtime/Messaging/INetworkMessageSender.cs
@@ -1,6 +1,6 @@
namespace Unity.Netcode
{
- internal interface IMessageSender
+ internal interface INetworkMessageSender
{
void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData);
}
diff --git a/Runtime/Messaging/IMessageSender.cs.meta b/Runtime/Messaging/INetworkMessageSender.cs.meta
similarity index 100%
rename from Runtime/Messaging/IMessageSender.cs.meta
rename to Runtime/Messaging/INetworkMessageSender.cs.meta
diff --git a/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs
index 417347b..d52de3b 100644
--- a/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs
+++ b/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs
@@ -24,7 +24,7 @@ namespace Unity.Netcode
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
- networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
return false;
}
diff --git a/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs
index e0b3769..648573e 100644
--- a/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs
+++ b/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs
@@ -84,18 +84,18 @@ namespace Unity.Netcode
{
var messageVersion = new MessageVersionData();
messageVersion.Deserialize(reader);
- networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
+ networkManager.ConnectionManager.MessageManager.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
messageHashesInOrder[i] = messageVersion.Hash;
// Update the received version since this message will always be passed version 0, due to the map not
// being initialized until just now.
- var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
+ var messageType = networkManager.ConnectionManager.MessageManager.GetMessageForHash(messageVersion.Hash);
if (messageType == typeof(ConnectionApprovedMessage))
{
receivedMessageVersion = messageVersion.Version;
}
}
- networkManager.MessagingSystem.SetServerMessageOrder(messageHashesInOrder);
+ networkManager.ConnectionManager.MessageManager.SetServerMessageOrder(messageHashesInOrder);
messageHashesInOrder.Dispose();
// ============================================================
// END FORBIDDEN SEGMENT
@@ -117,8 +117,11 @@ namespace Unity.Netcode
networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport.
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
- networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
- networkManager.IsApproved = true;
+ networkManager.ConnectionManager.LocalClient.SetRole(false, true, networkManager);
+ networkManager.ConnectionManager.LocalClient.IsApproved = true;
+ networkManager.ConnectionManager.LocalClient.ClientId = OwnerClientId;
+ // Stop the client-side approval timeout coroutine since we are approved.
+ networkManager.ConnectionManager.StopClientApprovalCoroutine();
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
if (!networkManager.NetworkConfig.EnableSceneManagement)
@@ -138,7 +141,7 @@ namespace Unity.Netcode
// Mark the client being connected
networkManager.IsConnectedClient = true;
// When scene management is disabled we notify after everything is synchronized
- networkManager.InvokeOnClientConnectedCallback(context.SenderId);
+ networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
}
}
}
diff --git a/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/Runtime/Messaging/Messages/ConnectionRequestMessage.cs
index 73e8b1f..8d558bf 100644
--- a/Runtime/Messaging/Messages/ConnectionRequestMessage.cs
+++ b/Runtime/Messaging/Messages/ConnectionRequestMessage.cs
@@ -59,11 +59,11 @@ namespace Unity.Netcode
{
var messageVersion = new MessageVersionData();
messageVersion.Deserialize(reader);
- networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
+ networkManager.ConnectionManager.MessageManager.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
// Update the received version since this message will always be passed version 0, due to the map not
// being initialized until just now.
- var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
+ var messageType = networkManager.ConnectionManager.MessageManager.GetMessageForHash(messageVersion.Hash);
if (messageType == typeof(ConnectionRequestMessage))
{
receivedMessageVersion = messageVersion.Version;
@@ -135,7 +135,7 @@ namespace Unity.Netcode
var networkManager = (NetworkManager)context.SystemOwner;
var senderId = context.SenderId;
- if (networkManager.PendingClients.TryGetValue(senderId, out PendingClient client))
+ if (networkManager.ConnectionManager.PendingClients.TryGetValue(senderId, out PendingClient client))
{
// Set to pending approval to prevent future connection requests from being approved
client.ConnectionState = PendingClient.State.PendingApproval;
@@ -143,17 +143,8 @@ namespace Unity.Netcode
if (networkManager.NetworkConfig.ConnectionApproval)
{
- // Note: Delegate creation allocates.
- // Note: ToArray() also allocates. :(
- var response = new NetworkManager.ConnectionApprovalResponse();
- networkManager.ClientsToApprove[senderId] = response;
-
- networkManager.ConnectionApprovalCallback(
- new NetworkManager.ConnectionApprovalRequest
- {
- Payload = ConnectionData,
- ClientNetworkId = senderId
- }, response);
+ var messageRequest = this;
+ networkManager.ConnectionManager.ApproveConnection(ref messageRequest, ref context);
}
else
{
@@ -162,7 +153,7 @@ namespace Unity.Netcode
Approved = true,
CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null
};
- networkManager.HandleConnectionApproval(senderId, response);
+ networkManager.ConnectionManager.HandleConnectionApproval(senderId, response);
}
}
}
diff --git a/Runtime/Messaging/Messages/CreateObjectMessage.cs b/Runtime/Messaging/Messages/CreateObjectMessage.cs
index defddd5..b285413 100644
--- a/Runtime/Messaging/Messages/CreateObjectMessage.cs
+++ b/Runtime/Messaging/Messages/CreateObjectMessage.cs
@@ -23,7 +23,7 @@ namespace Unity.Netcode
ObjectInfo.Deserialize(reader);
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
{
- networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context);
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context);
return false;
}
m_ReceivedNetworkVariableData = reader;
diff --git a/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/Runtime/Messaging/Messages/DestroyObjectMessage.cs
index 425cbe9..b02b42c 100644
--- a/Runtime/Messaging/Messages/DestroyObjectMessage.cs
+++ b/Runtime/Messaging/Messages/DestroyObjectMessage.cs
@@ -26,7 +26,7 @@ namespace Unity.Netcode
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
- networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
return false;
}
return true;
diff --git a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs
index 78b2983..68e64cd 100644
--- a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs
+++ b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs
@@ -67,8 +67,8 @@ namespace Unity.Netcode
// The object containing the behaviour we're about to process is about to be shown to this client
// As a result, the client will get the fully serialized NetworkVariable and would be confused by
// an extraneous delta
- if (NetworkBehaviour.NetworkManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
- NetworkBehaviour.NetworkManager.ObjectsToShowToClient[TargetClientId]
+ if (NetworkBehaviour.NetworkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
+ NetworkBehaviour.NetworkManager.SpawnManager.ObjectsToShowToClient[TargetClientId]
.Contains(NetworkBehaviour.NetworkObject))
{
shouldWrite = false;
@@ -90,7 +90,7 @@ namespace Unity.Netcode
{
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
- var tempWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
+ var tempWriter = new FastBufferWriter(NetworkBehaviour.NetworkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, NetworkBehaviour.NetworkManager.MessageManager.FragmentedMessageMaxSize);
NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
@@ -234,7 +234,7 @@ namespace Unity.Netcode
}
else
{
- networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
}
}
}
diff --git a/Runtime/Messaging/Messages/ParentSyncMessage.cs b/Runtime/Messaging/Messages/ParentSyncMessage.cs
index c65c87b..abbe888 100644
--- a/Runtime/Messaging/Messages/ParentSyncMessage.cs
+++ b/Runtime/Messaging/Messages/ParentSyncMessage.cs
@@ -85,7 +85,7 @@ namespace Unity.Netcode
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
- networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
return false;
}
return true;
diff --git a/Runtime/Messaging/Messages/RpcMessages.cs b/Runtime/Messaging/Messages/RpcMessages.cs
index dd6b199..1e91b06 100644
--- a/Runtime/Messaging/Messages/RpcMessages.cs
+++ b/Runtime/Messaging/Messages/RpcMessages.cs
@@ -23,7 +23,7 @@ namespace Unity.Netcode
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
{
- networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context);
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context);
return false;
}
diff --git a/Runtime/Messaging/BatchHeader.cs b/Runtime/Messaging/NetworkBatchHeader.cs
similarity index 91%
rename from Runtime/Messaging/BatchHeader.cs
rename to Runtime/Messaging/NetworkBatchHeader.cs
index 63b79fe..1039ce1 100644
--- a/Runtime/Messaging/BatchHeader.cs
+++ b/Runtime/Messaging/NetworkBatchHeader.cs
@@ -3,7 +3,7 @@ namespace Unity.Netcode
///
/// Header placed at the start of each message batch
///
- internal struct BatchHeader : INetworkSerializeByMemcpy
+ internal struct NetworkBatchHeader : INetworkSerializeByMemcpy
{
internal const ushort MagicValue = 0x1160;
///
diff --git a/Runtime/Messaging/BatchHeader.cs.meta b/Runtime/Messaging/NetworkBatchHeader.cs.meta
similarity index 100%
rename from Runtime/Messaging/BatchHeader.cs.meta
rename to Runtime/Messaging/NetworkBatchHeader.cs.meta
diff --git a/Runtime/Messaging/NetworkContext.cs b/Runtime/Messaging/NetworkContext.cs
index 8ddc3cb..05f0c8b 100644
--- a/Runtime/Messaging/NetworkContext.cs
+++ b/Runtime/Messaging/NetworkContext.cs
@@ -6,7 +6,7 @@ namespace Unity.Netcode
internal ref struct NetworkContext
{
///
- /// An opaque object used to represent the owner of the MessagingSystem that's receiving the message.
+ /// An opaque object used to represent the owner of the NetworkMessageManager that's receiving the message.
/// Outside of testing environments, the type of this variable will be
///
public object SystemOwner;
@@ -24,7 +24,7 @@ namespace Unity.Netcode
///
/// The header data that was sent with the message
///
- public MessageHeader Header;
+ public NetworkMessageHeader Header;
///
/// The actual serialized size of the header when packed into the buffer
diff --git a/Runtime/Messaging/NetworkManagerHooks.cs b/Runtime/Messaging/NetworkManagerHooks.cs
new file mode 100644
index 0000000..3cbd7d6
--- /dev/null
+++ b/Runtime/Messaging/NetworkManagerHooks.cs
@@ -0,0 +1,119 @@
+using System;
+
+namespace Unity.Netcode
+{
+ internal class NetworkManagerHooks : INetworkHooks
+ {
+ private NetworkManager m_NetworkManager;
+
+ internal NetworkManagerHooks(NetworkManager manager)
+ {
+ m_NetworkManager = manager;
+ }
+
+ public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
+ {
+ }
+
+ public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
+ {
+ }
+
+ public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
+ {
+ }
+
+ public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
+ {
+ }
+
+ public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
+ {
+ }
+
+ public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
+ {
+ }
+
+ public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
+ {
+ }
+
+ public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
+ {
+ }
+
+ public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
+ {
+ return !m_NetworkManager.MessageManager.StopProcessing;
+ }
+
+ public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
+ {
+ if (m_NetworkManager.IsServer)
+ {
+ if (messageType == typeof(ConnectionApprovedMessage))
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
+ }
+
+ return false;
+ }
+
+ if (m_NetworkManager.ConnectionManager.PendingClients.TryGetValue(senderId, out PendingClient client) && (client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted");
+ }
+
+ return false;
+ }
+
+ if (m_NetworkManager.ConnectedClients.TryGetValue(senderId, out NetworkClient connectedClient) && messageType == typeof(ConnectionRequestMessage))
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
+ }
+
+ return false;
+ }
+ }
+ else
+ {
+ if (messageType == typeof(ConnectionRequestMessage))
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
+ }
+
+ return false;
+ }
+
+ if (m_NetworkManager.IsConnectedClient && messageType == typeof(ConnectionApprovedMessage))
+ {
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
+ }
+
+ return false;
+ }
+ }
+
+ return !m_NetworkManager.MessageManager.StopProcessing;
+ }
+
+ public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage
+ {
+ }
+
+ public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage
+ {
+ }
+ }
+}
diff --git a/Runtime/Messaging/NetworkManagerHooks.cs.meta b/Runtime/Messaging/NetworkManagerHooks.cs.meta
new file mode 100644
index 0000000..faee38c
--- /dev/null
+++ b/Runtime/Messaging/NetworkManagerHooks.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c2be7fa492911d549bfca52be96c0906
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Messaging/MessageHeader.cs b/Runtime/Messaging/NetworkMessageHeader.cs
similarity index 61%
rename from Runtime/Messaging/MessageHeader.cs
rename to Runtime/Messaging/NetworkMessageHeader.cs
index 993616e..d0c9ae3 100644
--- a/Runtime/Messaging/MessageHeader.cs
+++ b/Runtime/Messaging/NetworkMessageHeader.cs
@@ -3,13 +3,11 @@ namespace Unity.Netcode
///
/// This is the header data that's serialized to the network when sending an
///
- internal struct MessageHeader : INetworkSerializeByMemcpy
+ internal struct NetworkMessageHeader : INetworkSerializeByMemcpy
{
///
- /// The byte representation of the message type. This is automatically assigned to each message
- /// by the MessagingSystem. This value is deterministic only so long as the list of messages remains
- /// unchanged - if new messages are added or messages are removed, MessageType assignments may be
- /// calculated differently.
+ /// The byte representation of the message type. This is automatically assigned to each message by the NetworkMessageManager.
+ /// This value is deterministic only so long as the list of messages remains unchanged - if new messages are added or messages are removed, MessageType assignments may be calculated differently.
///
public uint MessageType;
diff --git a/Runtime/Messaging/MessageHeader.cs.meta b/Runtime/Messaging/NetworkMessageHeader.cs.meta
similarity index 100%
rename from Runtime/Messaging/MessageHeader.cs.meta
rename to Runtime/Messaging/NetworkMessageHeader.cs.meta
diff --git a/Runtime/Messaging/MessagingSystem.cs b/Runtime/Messaging/NetworkMessageManager.cs
similarity index 83%
rename from Runtime/Messaging/MessagingSystem.cs
rename to Runtime/Messaging/NetworkMessageManager.cs
index 87f02ce..4722ea1 100644
--- a/Runtime/Messaging/MessagingSystem.cs
+++ b/Runtime/Messaging/NetworkMessageManager.cs
@@ -11,22 +11,34 @@ namespace Unity.Netcode
{
internal class HandlerNotRegisteredException : SystemException
{
- public HandlerNotRegisteredException() { }
- public HandlerNotRegisteredException(string issue) : base(issue) { }
+ public HandlerNotRegisteredException()
+ {
+ }
+
+ public HandlerNotRegisteredException(string issue) : base(issue)
+ {
+ }
}
internal class InvalidMessageStructureException : SystemException
{
- public InvalidMessageStructureException() { }
- public InvalidMessageStructureException(string issue) : base(issue) { }
+ public InvalidMessageStructureException()
+ {
+ }
+
+ public InvalidMessageStructureException(string issue) : base(issue)
+ {
+ }
}
- internal class MessagingSystem : IDisposable
+ internal class NetworkMessageManager : IDisposable
{
+ public bool StopProcessing = false;
+
private struct ReceiveQueueItem
{
public FastBufferReader Reader;
- public MessageHeader Header;
+ public NetworkMessageHeader Header;
public ulong SenderId;
public float Timestamp;
public int MessageHeaderSerializedSize;
@@ -34,7 +46,7 @@ namespace Unity.Netcode
private struct SendQueueItem
{
- public BatchHeader BatchHeader;
+ public NetworkBatchHeader BatchHeader;
public FastBufferWriter Writer;
public readonly NetworkDelivery NetworkDelivery;
@@ -42,11 +54,12 @@ namespace Unity.Netcode
{
Writer = new FastBufferWriter(writerSize, writerAllocator, maxWriterSize);
NetworkDelivery = delivery;
- BatchHeader = new BatchHeader { Magic = BatchHeader.MagicValue };
+ BatchHeader = new NetworkBatchHeader { Magic = NetworkBatchHeader.MagicValue };
}
}
- internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system);
+ internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, NetworkMessageManager manager);
+
internal delegate int VersionGetter();
private NativeList m_IncomingMessageQueue = new NativeList(16, Allocator.Persistent);
@@ -58,6 +71,8 @@ namespace Unity.Netcode
private Dictionary m_MessageTypes = new Dictionary();
private Dictionary> m_SendQueues = new Dictionary>();
+ private HashSet m_DisconnectedClients = new HashSet();
+
// This is m_PerClientMessageVersion[clientId][messageType] = version
private Dictionary> m_PerClientMessageVersions = new Dictionary>();
private Dictionary m_MessagesByHash = new Dictionary();
@@ -67,7 +82,7 @@ namespace Unity.Netcode
private uint m_HighMessageType;
private object m_Owner;
- private IMessageSender m_MessageSender;
+ private INetworkMessageSender m_Sender;
private bool m_Disposed;
internal Type[] MessageTypes => m_ReverseTypeMap;
@@ -80,8 +95,9 @@ namespace Unity.Netcode
return m_MessageTypes[t];
}
- public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
- public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
+ public const int DefaultNonFragmentedMessageMaxSize = 1300;
+ public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize;
+ public int FragmentedMessageMaxSize = int.MaxValue;
internal struct MessageWithHandler
{
@@ -94,7 +110,7 @@ namespace Unity.Netcode
{
var prioritizedTypes = new List();
- // first pass puts the priority message in the first indices
+ // First pass puts the priority message in the first indices
// Those are the messages that must be delivered in order to allow re-ordering the others later
foreach (var t in allowedTypes)
{
@@ -117,17 +133,18 @@ namespace Unity.Netcode
return prioritizedTypes;
}
- public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null)
+ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetworkMessageProvider provider = null)
{
try
{
- m_MessageSender = messageSender;
+ m_Sender = sender;
m_Owner = owner;
if (provider == null)
{
provider = new ILPPMessageProvider();
}
+
var allowedTypes = provider.GetMessages();
allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
@@ -144,20 +161,21 @@ namespace Unity.Netcode
}
}
- public unsafe void Dispose()
+ public void Dispose()
{
if (m_Disposed)
{
return;
}
- // Can't just iterate SendQueues or SendQueues.Keys because ClientDisconnected removes
- // from the queue.
+ // Can't just iterate SendQueues or SendQueues.Keys because ClientDisconnected removes from the queue.
foreach (var kvp in m_SendQueues)
{
- CleanupDisconnectedClient(kvp.Key);
+ ClientDisconnected(kvp.Key);
}
+ CleanupDisconnectedClients();
+
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
{
// Avoid copies...
@@ -169,7 +187,7 @@ namespace Unity.Netcode
m_Disposed = true;
}
- ~MessagingSystem()
+ ~NetworkMessageManager()
{
Dispose();
}
@@ -186,7 +204,7 @@ namespace Unity.Netcode
private void RegisterMessageType(MessageWithHandler messageWithHandler)
{
- // if we are out of space, perform amortized linear growth
+ // If we are out of space, perform amortized linear growth
if (m_HighMessageType == m_MessageHandlers.Length)
{
Array.Resize(ref m_MessageHandlers, 2 * m_MessageHandlers.Length);
@@ -220,19 +238,18 @@ namespace Unity.Netcode
{
unsafe
{
- fixed (byte* nativeData = data.Array)
+ fixed (byte* dataPtr = data.Array)
{
- var batchReader =
- new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
- if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
+ var batchReader = new FastBufferReader(dataPtr + data.Offset, Allocator.None, data.Count);
+ if (!batchReader.TryBeginRead(sizeof(NetworkBatchHeader)))
{
NetworkLog.LogError("Received a packet too small to contain a BatchHeader. Ignoring it.");
return;
}
- batchReader.ReadValue(out BatchHeader batchHeader);
+ batchReader.ReadValue(out NetworkBatchHeader batchHeader);
- if (batchHeader.Magic != BatchHeader.MagicValue)
+ if (batchHeader.Magic != NetworkBatchHeader.MagicValue)
{
NetworkLog.LogError($"Received a packet with an invalid Magic Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Offset: {data.Offset}, Size: {data.Count}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
return;
@@ -259,8 +276,7 @@ namespace Unity.Netcode
for (var messageIdx = 0; messageIdx < batchHeader.BatchCount; ++messageIdx)
{
-
- var messageHeader = new MessageHeader();
+ var messageHeader = new NetworkMessageHeader();
var position = batchReader.Position;
try
{
@@ -280,19 +296,20 @@ namespace Unity.Netcode
NetworkLog.LogError("Received a message that claimed a size larger than the packet, ending early!");
return;
}
+
m_IncomingMessageQueue.Add(new ReceiveQueueItem
{
Header = messageHeader,
SenderId = clientId,
Timestamp = receiveTime,
// 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.
+ // 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, (int)messageHeader.MessageSize),
MessageHeaderSerializedSize = receivedHeaderSize,
});
batchReader.Seek(batchReader.Position + (int)messageHeader.MessageSize);
}
+
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchCount, batchReader.Length);
@@ -320,6 +337,7 @@ namespace Unity.Netcode
{
return null;
}
+
return m_MessagesByHash[messageHash];
}
@@ -329,6 +347,7 @@ namespace Unity.Netcode
{
return;
}
+
var messageType = m_MessagesByHash[messageHash];
if (!m_PerClientMessageVersions.ContainsKey(clientId))
@@ -353,6 +372,7 @@ namespace Unity.Netcode
{
continue;
}
+
var messageType = m_MessagesByHash[messagesInIdOrder[i]];
var oldId = oldTypes[messageType];
var handler = oldHandlers[oldId];
@@ -363,7 +383,7 @@ namespace Unity.Netcode
}
}
- public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
+ public void HandleMessage(in NetworkMessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{
using (reader)
{
@@ -372,6 +392,7 @@ namespace Unity.Netcode
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
return;
}
+
var context = new NetworkContext
{
SystemOwner = m_Owner,
@@ -391,13 +412,12 @@ namespace Unity.Netcode
var handler = m_MessageHandlers[header.MessageType];
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
- m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize());
+ m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize());
}
- // This will also log an exception is if the server knows about a message type the client doesn't know
- // about. In this case the handler will be null. It is still an issue the user must deal with: If the
- // two connecting builds know about different messages, the server should not send a message to a client
- // that doesn't know about it
+ // This will also log an exception is if the server knows about a message type the client doesn't know about.
+ // In this case the handler will be null. It is still an issue the user must deal with:
+ // If the two connecting builds know about different messages, the server should not send a message to a client that doesn't know about it
if (handler == null)
{
Debug.LogException(new HandlerNotRegisteredException(header.MessageType.ToString()));
@@ -406,9 +426,7 @@ namespace Unity.Netcode
{
// No user-land message handler exceptions should escape the receive loop.
// If an exception is throw, the message is ignored.
- // Example use case: A bad message is received that can't be deserialized and throws
- // an OverflowException because it specifies a length greater than the number of bytes in it
- // for some dynamic-length value.
+ // Example use case: A bad message is received that can't be deserialized and throws an OverflowException because it specifies a length greater than the number of bytes in it for some dynamic-length value.
try
{
handler.Invoke(reader, ref context, this);
@@ -418,15 +436,21 @@ namespace Unity.Netcode
Debug.LogException(e);
}
}
+
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
- m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize());
+ m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize());
}
}
}
- internal unsafe void ProcessIncomingMessageQueue()
+ internal void ProcessIncomingMessageQueue()
{
+ if (StopProcessing)
+ {
+ return;
+ }
+
for (var index = 0; index < m_IncomingMessageQueue.Length; ++index)
{
// Avoid copies...
@@ -447,21 +471,22 @@ namespace Unity.Netcode
{
return;
}
+
m_SendQueues[clientId] = new NativeList(16, Allocator.Persistent);
}
internal void ClientDisconnected(ulong clientId)
{
- if (!m_SendQueues.ContainsKey(clientId))
- {
- return;
- }
- CleanupDisconnectedClient(clientId);
- m_SendQueues.Remove(clientId);
+ m_DisconnectedClients.Add(clientId);
}
private void CleanupDisconnectedClient(ulong clientId)
{
+ if (!m_SendQueues.ContainsKey(clientId))
+ {
+ return;
+ }
+
var queue = m_SendQueues[clientId];
for (var i = 0; i < queue.Length; ++i)
{
@@ -469,23 +494,19 @@ namespace Unity.Netcode
}
queue.Dispose();
+ m_SendQueues.Remove(clientId);
+
+ m_PerClientMessageVersions.Remove(clientId);
}
internal void CleanupDisconnectedClients()
{
- var removeList = new NativeList(Allocator.Temp);
- foreach (var clientId in m_PerClientMessageVersions.Keys)
+ foreach (var clientId in m_DisconnectedClients)
{
- if (!m_SendQueues.ContainsKey(clientId))
- {
- removeList.Add(clientId);
- }
+ CleanupDisconnectedClient(clientId);
}
- foreach (var clientId in removeList)
- {
- m_PerClientMessageVersions.Remove(clientId);
- }
+ m_DisconnectedClients.Clear();
}
public static int CreateMessageAndGetVersion() where T : INetworkMessage, new()
@@ -500,14 +521,15 @@ namespace Unity.Netcode
if (forReceive)
{
Debug.LogWarning($"Trying to receive {type.Name} from client {clientId} which is not in a connected state.");
-
}
else
{
Debug.LogWarning($"Trying to send {type.Name} to client {clientId} which is not in a connected state.");
}
+
return -1;
}
+
if (!versionMap.TryGetValue(type, out var messageVersion))
{
return -1;
@@ -516,7 +538,7 @@ namespace Unity.Netcode
return messageVersion;
}
- public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new()
+ public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, NetworkMessageManager manager) where T : INetworkMessage, new()
{
var message = new T();
var messageVersion = 0;
@@ -525,24 +547,25 @@ namespace Unity.Netcode
// and can't change.
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage))
{
- messageVersion = system.GetMessageVersion(typeof(T), context.SenderId, true);
+ messageVersion = manager.GetMessageVersion(typeof(T), context.SenderId, true);
if (messageVersion < 0)
{
return;
}
}
+
if (message.Deserialize(reader, ref context, messageVersion))
{
- for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
+ for (var hookIdx = 0; hookIdx < manager.m_Hooks.Count; ++hookIdx)
{
- system.m_Hooks[hookIdx].OnBeforeHandleMessage(ref message, ref context);
+ manager.m_Hooks[hookIdx].OnBeforeHandleMessage(ref message, ref context);
}
message.Handle(ref context);
- for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
+ for (var hookIdx = 0; hookIdx < manager.m_Hooks.Count; ++hookIdx)
{
- system.m_Hooks[hookIdx].OnAfterHandleMessage(ref message, ref context);
+ manager.m_Hooks[hookIdx].OnAfterHandleMessage(ref message, ref context);
}
}
}
@@ -574,9 +597,8 @@ namespace Unity.Netcode
for (var i = 0; i < clientIds.Count; ++i)
{
var messageVersion = 0;
- // Special case because this is the message that carries the version info - thus the version info isn't
- // populated yet when we get this. The first part of this message always has to be the version data
- // and can't change.
+ // Special case because this is the message that carries the version info - thus the version info isn't populated yet when we get this.
+ // The first part of this message always has to be the version data and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
@@ -594,9 +616,9 @@ namespace Unity.Netcode
sentMessageVersions.Add(messageVersion);
- var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
+ var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FragmentedMessageMaxSize : NonFragmentedMessageMaxSize;
- using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize());
+ using var tmpSerializer = new FastBufferWriter(NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize());
message.Serialize(tmpSerializer, messageVersion);
@@ -612,9 +634,9 @@ namespace Unity.Netcode
internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList clientIds, int messageVersionFilter)
where TMessageType : INetworkMessage
{
- using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize(), Allocator.Temp);
+ using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize(), Allocator.Temp);
- var header = new MessageHeader
+ var header = new NetworkMessageHeader
{
MessageSize = (uint)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
@@ -624,13 +646,16 @@ namespace Unity.Netcode
for (var i = 0; i < clientIds.Count; ++i)
{
- var messageVersion = 0;
- // Special case because this is the message that carries the version info - thus the version info isn't
- // populated yet when we get this. The first part of this message always has to be the version data
- // and can't change.
+ if (m_DisconnectedClients.Contains(clientIds[i]))
+ {
+ continue;
+ }
+
+ // Special case because this is the message that carries the version info - thus the version info isn't populated yet when we get this.
+ // The first part of this message always has to be the version data and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
- messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
+ var messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
if (messageVersion < 0)
{
// Client doesn't know this message exists, don't send it at all.
@@ -658,20 +683,16 @@ namespace Unity.Netcode
var sendQueueItem = m_SendQueues[clientId];
if (sendQueueItem.Length == 0)
{
- sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
- maxSize));
- sendQueueItem.ElementAt(0).Writer.Seek(sizeof(BatchHeader));
+ sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize));
+ sendQueueItem.ElementAt(0).Writer.Seek(sizeof(NetworkBatchHeader));
}
else
{
ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
- if (lastQueueItem.NetworkDelivery != delivery ||
- lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
- < tmpSerializer.Length + headerSerializer.Length)
+ 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.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
+ sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize));
+ sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(NetworkBatchHeader));
}
}
@@ -768,6 +789,11 @@ namespace Unity.Netcode
internal unsafe void ProcessSendQueues()
{
+ if (StopProcessing)
+ {
+ return;
+ }
+
foreach (var kvp in m_SendQueues)
{
var clientId = kvp.Key;
@@ -775,6 +801,15 @@ namespace Unity.Netcode
for (var i = 0; i < sendQueueItem.Length; ++i)
{
ref var queueItem = ref sendQueueItem.ElementAt(i);
+ // This is checked at every iteration because
+ // 1) each writer needs to be disposed, so we have to do the full loop regardless, and
+ // 2) the call to m_MessageSender.Send() may result in calling ClientDisconnected(), so the result of this check may change partway through iteration
+ if (m_DisconnectedClients.Contains(clientId))
+ {
+ queueItem.Writer.Dispose();
+ continue;
+ }
+
if (queueItem.BatchHeader.BatchCount == 0)
{
queueItem.Writer.Dispose();
@@ -789,9 +824,9 @@ namespace Unity.Netcode
queueItem.Writer.Seek(0);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// Skipping the Verify and sneaking the write mark in because we know it's fine.
- queueItem.Writer.Handle->AllowedWriteMark = sizeof(BatchHeader);
+ queueItem.Writer.Handle->AllowedWriteMark = sizeof(NetworkBatchHeader);
#endif
- queueItem.BatchHeader.BatchHash = XXHash.Hash64(queueItem.Writer.GetUnsafePtr() + sizeof(BatchHeader), queueItem.Writer.Length - sizeof(BatchHeader));
+ queueItem.BatchHeader.BatchHash = XXHash.Hash64(queueItem.Writer.GetUnsafePtr() + sizeof(NetworkBatchHeader), queueItem.Writer.Length - sizeof(NetworkBatchHeader));
queueItem.BatchHeader.BatchSize = queueItem.Writer.Length;
@@ -800,7 +835,7 @@ namespace Unity.Netcode
try
{
- m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
+ m_Sender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
@@ -812,6 +847,7 @@ namespace Unity.Netcode
queueItem.Writer.Dispose();
}
}
+
sendQueueItem.Clear();
}
}
diff --git a/Runtime/Messaging/MessagingSystem.cs.meta b/Runtime/Messaging/NetworkMessageManager.cs.meta
similarity index 100%
rename from Runtime/Messaging/MessagingSystem.cs.meta
rename to Runtime/Messaging/NetworkMessageManager.cs.meta
diff --git a/Runtime/Metrics/NetworkMetricsManager.cs b/Runtime/Metrics/NetworkMetricsManager.cs
new file mode 100644
index 0000000..2a13739
--- /dev/null
+++ b/Runtime/Metrics/NetworkMetricsManager.cs
@@ -0,0 +1,44 @@
+#if MULTIPLAYER_TOOLS
+using Unity.Multiplayer.Tools;
+#endif
+
+namespace Unity.Netcode
+{
+ ///
+ /// This probably needs to all be migrated into , but
+ /// keeping it separated for the time being
+ ///
+ internal class NetworkMetricsManager
+ {
+ internal INetworkMetrics NetworkMetrics { get; private set; }
+
+ private NetworkManager m_NetworkManager;
+
+ public void UpdateMetrics()
+ {
+ NetworkMetrics.UpdateNetworkObjectsCount(m_NetworkManager.SpawnManager.SpawnedObjects.Count);
+ NetworkMetrics.UpdateConnectionsCount((m_NetworkManager.IsServer) ? m_NetworkManager.ConnectionManager.ConnectedClients.Count : 1);
+ NetworkMetrics.DispatchFrame();
+ }
+
+ public void Initialize(NetworkManager networkManager)
+ {
+ m_NetworkManager = networkManager;
+ if (NetworkMetrics == null)
+ {
+#if MULTIPLAYER_TOOLS
+ NetworkMetrics = new NetworkMetrics();
+#else
+ NetworkMetrics = new NullNetworkMetrics();
+#endif
+ }
+
+#if MULTIPLAYER_TOOLS
+ NetworkSolutionInterface.SetInterface(new NetworkSolutionInterfaceParameters
+ {
+ NetworkObjectProvider = new NetworkObjectProvider(networkManager),
+ });
+#endif
+ }
+ }
+}
diff --git a/Runtime/Metrics/NetworkMetricsManager.cs.meta b/Runtime/Metrics/NetworkMetricsManager.cs.meta
new file mode 100644
index 0000000..99b479a
--- /dev/null
+++ b/Runtime/Metrics/NetworkMetricsManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3b20a0fe127a24d48867c2ee448bdb1b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/NetworkVariable/Collections/NetworkList.cs b/Runtime/NetworkVariable/Collections/NetworkList.cs
index aa25390..aee004f 100644
--- a/Runtime/NetworkVariable/Collections/NetworkList.cs
+++ b/Runtime/NetworkVariable/Collections/NetworkList.cs
@@ -75,7 +75,7 @@ namespace Unity.Netcode
return;
}
- m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
+ m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
}
///
@@ -490,12 +490,14 @@ namespace Unity.Netcode
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
+ var value = m_List[index];
m_List.RemoveAt(index);
var listEvent = new NetworkListEvent()
{
Type = NetworkListEvent.EventType.RemoveAt,
- Index = index
+ Index = index,
+ Value = value
};
HandleAddListEvent(listEvent);
diff --git a/Runtime/NetworkVariable/NetworkVariable.cs b/Runtime/NetworkVariable/NetworkVariable.cs
index 538d6f3..505df39 100644
--- a/Runtime/NetworkVariable/NetworkVariable.cs
+++ b/Runtime/NetworkVariable/NetworkVariable.cs
@@ -33,6 +33,13 @@ namespace Unity.Netcode
: base(readPerm, writePerm)
{
m_InternalValue = value;
+ // Since we start with IsDirty = true, this doesn't need to be duplicated
+ // right away. It won't get read until after ResetDirty() is called, and
+ // the duplicate will be made there. Avoiding calling
+ // NetworkVariableSerialization.Duplicate() is important because calling
+ // it in the constructor might not give users enough time to set the
+ // DuplicateValue callback if they're using UserNetworkVariableSerialization
+ m_PreviousValue = default;
}
///
@@ -41,6 +48,11 @@ namespace Unity.Netcode
[SerializeField]
private protected T m_InternalValue;
+ private protected T m_PreviousValue;
+
+ private bool m_HasPreviousValue;
+ private bool m_IsDisposed;
+
///
/// The value of the NetworkVariable container
///
@@ -61,9 +73,83 @@ namespace Unity.Netcode
}
Set(value);
+ m_IsDisposed = false;
}
}
+ internal ref T RefValue()
+ {
+ return ref m_InternalValue;
+ }
+
+ public override void Dispose()
+ {
+ if (m_IsDisposed)
+ {
+ return;
+ }
+
+ m_IsDisposed = true;
+ if (m_InternalValue is IDisposable internalValueDisposable)
+ {
+ internalValueDisposable.Dispose();
+ }
+
+ m_InternalValue = default;
+ if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable)
+ {
+ m_HasPreviousValue = false;
+ previousValueDisposable.Dispose();
+ }
+
+ m_PreviousValue = default;
+ }
+
+ ~NetworkVariable()
+ {
+ Dispose();
+ }
+
+ ///
+ /// Gets Whether or not the container is dirty
+ ///
+ /// Whether or not the container is dirty
+ public override bool IsDirty()
+ {
+ // For most cases we can use the dirty flag.
+ // This doesn't work for cases where we're wrapping more complex types
+ // like INetworkSerializable, NativeList, NativeArray, etc.
+ // Changes to the values in those types don't call the Value.set method,
+ // so we can't catch those changes and need to compare the current value
+ // against the previous one.
+ if (base.IsDirty())
+ {
+ return true;
+ }
+
+ // Cache the dirty value so we don't perform this again if we already know we're dirty
+ // Unfortunately we can't cache the NOT dirty state, because that might change
+ // in between to checks... but the DIRTY state won't change until ResetDirty()
+ // is called.
+ var dirty = !NetworkVariableSerialization.AreEqual(ref m_PreviousValue, ref m_InternalValue);
+ SetDirty(dirty);
+ return dirty;
+ }
+
+ ///
+ /// Resets the dirty state and marks the variable as synced / clean
+ ///
+ public override void ResetDirty()
+ {
+ base.ResetDirty();
+ // Resetting the dirty value declares that the current value is not dirty
+ // Therefore, we set the m_PreviousValue field to a duplicate of the current
+ // field, so that our next dirty check is made against the current "not dirty"
+ // value.
+ m_HasPreviousValue = true;
+ NetworkVariableSerialization.Serializer.Duplicate(m_InternalValue, ref m_PreviousValue);
+ }
+
///
/// Sets the , marks the dirty, and invokes the callback
/// if there are subscribers to that event.
diff --git a/Runtime/NetworkVariable/NetworkVariableBase.cs b/Runtime/NetworkVariable/NetworkVariableBase.cs
index 7addee2..ece07f1 100644
--- a/Runtime/NetworkVariable/NetworkVariableBase.cs
+++ b/Runtime/NetworkVariable/NetworkVariableBase.cs
@@ -94,7 +94,8 @@ namespace Unity.Netcode
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
return;
}
- m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
+
+ m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
}
}
diff --git a/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/Runtime/NetworkVariable/NetworkVariableSerialization.cs
index 463f502..6ade408 100644
--- a/Runtime/NetworkVariable/NetworkVariableSerialization.cs
+++ b/Runtime/NetworkVariable/NetworkVariableSerialization.cs
@@ -20,6 +20,8 @@ namespace Unity.Netcode
// of it to pass it as a ref parameter.
public void Write(FastBufferWriter writer, ref T value);
public void Read(FastBufferReader reader, ref T value);
+ internal void ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator);
+ public void Duplicate(in T value, ref T duplicatedValue);
}
///
@@ -35,6 +37,16 @@ namespace Unity.Netcode
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out short value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in short value, ref short duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
///
@@ -50,6 +62,16 @@ namespace Unity.Netcode
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ushort value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in ushort value, ref ushort duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
///
@@ -65,6 +87,16 @@ namespace Unity.Netcode
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out int value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in int value, ref int duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
///
@@ -80,6 +112,16 @@ namespace Unity.Netcode
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out uint value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in uint value, ref uint duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
///
@@ -95,6 +137,16 @@ namespace Unity.Netcode
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out long value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in long value, ref long duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
///
@@ -110,6 +162,16 @@ namespace Unity.Netcode
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ulong value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in ulong value, ref ulong duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
///
@@ -130,8 +192,84 @@ namespace Unity.Netcode
{
reader.ReadUnmanagedSafe(out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in T value, ref T duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
+ internal class UnmanagedArraySerializer : INetworkVariableSerializer> where T : unmanaged
+ {
+ public void Write(FastBufferWriter writer, ref NativeArray value)
+ {
+ writer.WriteUnmanagedSafe(value);
+ }
+ public void Read(FastBufferReader reader, ref NativeArray value)
+ {
+ value.Dispose();
+ reader.ReadUnmanagedSafe(out value, Allocator.Persistent);
+ }
+
+ void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator)
+ {
+ reader.ReadUnmanagedSafe(out value, allocator);
+ }
+
+ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue)
+ {
+ if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length)
+ {
+ if (duplicatedValue.IsCreated)
+ {
+ duplicatedValue.Dispose();
+ }
+
+ duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ }
+
+ duplicatedValue.CopyFrom(value);
+ }
+ }
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ internal class UnmanagedListSerializer : INetworkVariableSerializer> where T : unmanaged
+ {
+ public void Write(FastBufferWriter writer, ref NativeList value)
+ {
+ writer.WriteUnmanagedSafe(value);
+ }
+ public void Read(FastBufferReader reader, ref NativeList value)
+ {
+ reader.ReadUnmanagedSafeInPlace(ref value);
+ }
+
+ void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in NativeList value, ref NativeList duplicatedValue)
+ {
+ if (!duplicatedValue.IsCreated)
+ {
+ duplicatedValue = new NativeList(value.Length, Allocator.Persistent);
+ }
+ else if (value.Length != duplicatedValue.Length)
+ {
+ duplicatedValue.ResizeUninitialized(value.Length);
+ }
+
+ duplicatedValue.CopyFrom(value);
+ }
+ }
+#endif
+
///
/// Serializer for FixedStrings
///
@@ -146,8 +284,92 @@ namespace Unity.Netcode
{
reader.ReadValueSafeInPlace(ref value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in T value, ref T duplicatedValue)
+ {
+ duplicatedValue = value;
+ }
}
+ ///
+ /// Serializer for FixedStrings
+ ///
+ ///
+ internal class FixedStringArraySerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes
+ {
+ public void Write(FastBufferWriter writer, ref NativeArray value)
+ {
+ writer.WriteValueSafe(value);
+ }
+ public void Read(FastBufferReader reader, ref NativeArray value)
+ {
+ value.Dispose();
+ reader.ReadValueSafe(out value, Allocator.Persistent);
+ }
+
+ void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator)
+ {
+ reader.ReadValueSafe(out value, allocator);
+ }
+
+ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue)
+ {
+ if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length)
+ {
+ if (duplicatedValue.IsCreated)
+ {
+ duplicatedValue.Dispose();
+ }
+
+ duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ }
+
+ duplicatedValue.CopyFrom(value);
+ }
+ }
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ ///
+ /// Serializer for FixedStrings
+ ///
+ ///
+ internal class FixedStringListSerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes
+ {
+ public void Write(FastBufferWriter writer, ref NativeList value)
+ {
+ writer.WriteValueSafe(value);
+ }
+ public void Read(FastBufferReader reader, ref NativeList value)
+ {
+ reader.ReadValueSafeInPlace(ref value);
+ }
+
+ void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in NativeList value, ref NativeList duplicatedValue)
+ {
+ if (!duplicatedValue.IsCreated)
+ {
+ duplicatedValue = new NativeList(value.Length, Allocator.Persistent);
+ }
+ else if (value.Length != duplicatedValue.Length)
+ {
+ duplicatedValue.ResizeUninitialized(value.Length);
+ }
+
+ duplicatedValue.CopyFrom(value);
+ }
+ }
+#endif
+
///
/// Serializer for unmanaged INetworkSerializable types
///
@@ -163,10 +385,93 @@ namespace Unity.Netcode
{
var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader));
value.NetworkSerialize(bufferSerializer);
+ }
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in T value, ref T duplicatedValue)
+ {
+ duplicatedValue = value;
}
}
+ ///
+ /// Serializer for unmanaged INetworkSerializable types
+ ///
+ ///
+ internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable
+ {
+ public void Write(FastBufferWriter writer, ref NativeArray value)
+ {
+ writer.WriteNetworkSerializable(value);
+ }
+ public void Read(FastBufferReader reader, ref NativeArray value)
+ {
+ value.Dispose();
+ reader.ReadNetworkSerializable(out value, Allocator.Persistent);
+ }
+
+ void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator)
+ {
+ reader.ReadNetworkSerializable(out value, allocator);
+ }
+
+ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue)
+ {
+ if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length)
+ {
+ if (duplicatedValue.IsCreated)
+ {
+ duplicatedValue.Dispose();
+ }
+
+ duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ }
+
+ duplicatedValue.CopyFrom(value);
+ }
+ }
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ ///
+ /// Serializer for unmanaged INetworkSerializable types
+ ///
+ ///
+ internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable
+ {
+ public void Write(FastBufferWriter writer, ref NativeList value)
+ {
+ writer.WriteNetworkSerializable(value);
+ }
+ public void Read(FastBufferReader reader, ref NativeList value)
+ {
+ reader.ReadNetworkSerializableInPlace(ref value);
+ }
+
+ void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in NativeList value, ref NativeList duplicatedValue)
+ {
+ if (!duplicatedValue.IsCreated)
+ {
+ duplicatedValue = new NativeList(value.Length, Allocator.Persistent);
+ }
+ else if (value.Length != duplicatedValue.Length)
+ {
+ duplicatedValue.ResizeUninitialized(value.Length);
+ }
+
+ duplicatedValue.CopyFrom(value);
+ }
+ }
+#endif
+
///
/// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it
/// has to be null-aware
@@ -201,6 +506,21 @@ namespace Unity.Netcode
value.NetworkSerialize(bufferSerializer);
}
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in T value, ref T duplicatedValue)
+ {
+ using var writer = new FastBufferWriter(256, Allocator.Temp);
+ var refValue = value;
+ Write(writer, ref refValue);
+
+ using var reader = new FastBufferReader(writer, Allocator.None);
+ Read(reader, ref duplicatedValue);
+ }
}
///
@@ -227,14 +547,26 @@ namespace Unity.Netcode
public delegate void ReadValueDelegate(FastBufferReader reader, out T value);
///
- /// The delegate handler declaration
+ /// The read value delegate handler definition
+ ///
+ /// The to read the value of type `T`
+ /// The value of type `T` to be read
+ public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue);
+
+ ///
+ /// Callback to write a value
///
public static WriteValueDelegate WriteValue;
///
- /// The delegate handler declaration
+ /// Callback to read a value
///
public static ReadValueDelegate ReadValue;
+
+ ///
+ /// Callback to create a duplicate of a value, used to check for dirty status.
+ ///
+ public static DuplicateValueDelegate DuplicateValue;
}
///
@@ -250,20 +582,34 @@ namespace Unity.Netcode
{
public void Write(FastBufferWriter writer, ref T value)
{
- if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null)
+ if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null)
{
- throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)} and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
+ throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
}
UserNetworkVariableSerialization.WriteValue(writer, value);
}
public void Read(FastBufferReader reader, ref T value)
{
- if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null)
+ if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null)
{
- throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)} and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
+ throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
}
UserNetworkVariableSerialization.ReadValue(reader, out value);
}
+
+ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Duplicate(in T value, ref T duplicatedValue)
+ {
+ if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null)
+ {
+ throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
+ }
+ UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue);
+ }
}
///
@@ -309,6 +655,26 @@ namespace Unity.Netcode
NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer();
}
+ ///
+ /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
+ ///
+ ///
+ public static void InitializeSerializer_UnmanagedByMemcpyArray() where T : unmanaged
+ {
+ NetworkVariableSerialization>.Serializer = new UnmanagedArraySerializer();
+ }
+
+#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ ///
+ /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
+ ///
+ ///
+ public static void InitializeSerializer_UnmanagedByMemcpyList() where T : unmanaged
+ {
+ NetworkVariableSerialization>.Serializer = new UnmanagedListSerializer();
+ }
+#endif
+
///
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
/// NetworkSerialize
@@ -319,6 +685,28 @@ namespace Unity.Netcode
NetworkVariableSerialization.Serializer = new UnmanagedNetworkSerializableSerializer();
}
+ ///
+ /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
+ /// NetworkSerialize
+ ///
+ ///
+ public static void InitializeSerializer_UnmanagedINetworkSerializableArray() where T : unmanaged, INetworkSerializable
+ {
+ NetworkVariableSerialization