3 Commits

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

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

## [1.0.0-pre.5] - 2022-01-26

### Added

- Added `PreviousValue` in `NetworkListEvent`, when `Value` has changed (#1528)

### Changed

- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)

### Fixed

- Fixed network tick value sometimes being duplicated or skipped. (#1614)
- Fixed The ClientNetworkTransform sample script to allow for owner changes at runtime. (#1606)
2022-01-26 00:00:00 +00:00
Unity Technologies
36d07fad5e com.unity.netcode.gameobjects@1.0.0-pre.4
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.0.0-pre.4] - 2021-01-04

### Added

- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)

### Removed

- Removed `com.unity.modules.ai` package dependency (#1565)
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)

### Fixed
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)

### Changed
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
- Updated com.unity.collections to 1.1.0 (#1451)
2021-01-04 00:00:00 +00:00
Unity Technologies
f5664b4cc1 com.unity.netcode.gameobjects@1.0.0-pre.3
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.0.0-pre.3] - 2021-10-22

### Added

- ResetTrigger function to NetworkAnimator (#1327)

### Fixed

- Overflow exception when syncing Animator state. (#1327)
- Added `try`/`catch` around RPC calls, preventing exception from causing further RPC calls to fail (#1329)
- Fixed an issue where ServerClientId and LocalClientId could have the same value, causing potential confusion, and also fixed an issue with the UNet where the server could be identified with two different values, one of which might be the same as LocalClientId, and the other of which would not.(#1368)
- IL2CPP would not properly compile (#1359)
2021-10-22 00:00:00 +00:00
73 changed files with 2380 additions and 932 deletions

View File

@@ -6,7 +6,68 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.0.0-pre.2] - 2020-12-20 ## [1.0.0-pre.5] - 2022-01-26
### Added
- Added `PreviousValue` in `NetworkListEvent`, when `Value` has changed (#1528)
### Changed
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)
### Fixed
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
- Fixed The ClientNetworkTransform sample script to allow for owner changes at runtime. (#1606)
## [1.0.0-pre.4] - 2021-01-04
### Added
- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)
### Removed
- Removed `com.unity.modules.ai` package dependency (#1565)
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
### Fixed
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
### Changed
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
- Updated com.unity.collections to 1.1.0 (#1451)
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)
## [1.0.0-pre.3] - 2021-10-22
### Added
- ResetTrigger function to NetworkAnimator (#1327)
### Fixed
- Overflow exception when syncing Animator state. (#1327)
- Added `try`/`catch` around RPC calls, preventing exception from causing further RPC calls to fail (#1329)
- Fixed an issue where ServerClientId and LocalClientId could have the same value, causing potential confusion, and also fixed an issue with the UNet where the server could be identified with two different values, one of which might be the same as LocalClientId, and the other of which would not.(#1368)
- IL2CPP would not properly compile (#1359)
## [1.0.0-pre.2] - 2021-10-19
### Added ### Added
@@ -16,7 +77,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Updated label for `1.0.0-pre.1` changelog section - Updated label for `1.0.0-pre.1` changelog section
## [1.0.0-pre.1] - 2020-12-20 ## [1.0.0-pre.1] - 2021-10-19
### Added ### Added

View File

@@ -1,5 +1,6 @@
using Unity.Collections; using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using UnityEngine; using UnityEngine;
namespace Unity.Netcode.Components namespace Unity.Netcode.Components
@@ -38,11 +39,12 @@ namespace Unity.Netcode.Components
internal struct AnimationTriggerMessage : INetworkSerializable internal struct AnimationTriggerMessage : INetworkSerializable
{ {
public int Hash; public int Hash;
public bool Reset;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{ {
serializer.SerializeValue(ref Hash); serializer.SerializeValue(ref Hash);
serializer.SerializeValue(ref Reset);
} }
} }
@@ -286,6 +288,12 @@ namespace Unity.Netcode.Components
return false; return false;
} }
/* $AS TODO: Right now we are not checking for changed values this is because
the read side of this function doesn't have similar logic which would cause
an overflow read because it doesn't know if the value is there or not. So
there needs to be logic to track which indexes changed in order for there
to be proper value change checking. Will revist in 1.1.0.
*/
private unsafe bool WriteParameters(FastBufferWriter writer, bool autoSend) private unsafe bool WriteParameters(FastBufferWriter writer, bool autoSend)
{ {
if (m_CachedAnimatorParameters == null) if (m_CachedAnimatorParameters == null)
@@ -307,43 +315,31 @@ namespace Unity.Netcode.Components
{ {
var valueInt = m_Animator.GetInteger(hash); var valueInt = m_Animator.GetInteger(hash);
fixed (void* value = cacheValue.Value) fixed (void* value = cacheValue.Value)
{
var oldValue = UnsafeUtility.AsRef<int>(value);
if (valueInt != oldValue)
{ {
UnsafeUtility.WriteArrayElement(value, 0, valueInt); UnsafeUtility.WriteArrayElement(value, 0, valueInt);
BytePacker.WriteValuePacked(writer, (uint)valueInt); BytePacker.WriteValuePacked(writer, (uint)valueInt);
} }
} }
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
{ {
var valueBool = m_Animator.GetBool(hash); var valueBool = m_Animator.GetBool(hash);
fixed (void* value = cacheValue.Value) fixed (void* value = cacheValue.Value)
{
var oldValue = UnsafeUtility.AsRef<bool>(value);
if (valueBool != oldValue)
{ {
UnsafeUtility.WriteArrayElement(value, 0, valueBool); UnsafeUtility.WriteArrayElement(value, 0, valueBool);
writer.WriteValueSafe(valueBool); writer.WriteValueSafe(valueBool);
} }
} }
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
{ {
var valueFloat = m_Animator.GetFloat(hash); var valueFloat = m_Animator.GetFloat(hash);
fixed (void* value = cacheValue.Value) fixed (void* value = cacheValue.Value)
{ {
var oldValue = UnsafeUtility.AsRef<float>(value);
if (valueFloat != oldValue)
{
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
writer.WriteValueSafe(valueFloat); writer.WriteValueSafe(valueFloat);
} }
} }
} }
}
// If we do not write any values to the writer then we should not send any data // If we do not write any values to the writer then we should not send any data
return writer.Length > 0; return writer.Length > 0;
@@ -431,24 +427,42 @@ namespace Unity.Netcode.Components
[ClientRpc] [ClientRpc]
private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default) private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{
if (animSnapshot.Reset)
{
m_Animator.ResetTrigger(animSnapshot.Hash);
}
else
{ {
m_Animator.SetTrigger(animSnapshot.Hash); m_Animator.SetTrigger(animSnapshot.Hash);
} }
}
public void SetTrigger(string triggerName) public void SetTrigger(string triggerName)
{ {
SetTrigger(Animator.StringToHash(triggerName)); SetTrigger(Animator.StringToHash(triggerName));
} }
public void SetTrigger(int hash) public void SetTrigger(int hash, bool reset = false)
{ {
var animMsg = new AnimationTriggerMessage(); var animMsg = new AnimationTriggerMessage();
animMsg.Hash = hash; animMsg.Hash = hash;
animMsg.Reset = reset;
if (IsServer) if (IsServer)
{ {
SendAnimTriggerClientRpc(animMsg); SendAnimTriggerClientRpc(animMsg);
} }
} }
public void ResetTrigger(string triggerName)
{
ResetTrigger(Animator.StringToHash(triggerName));
}
public void ResetTrigger(int hash)
{
SetTrigger(hash, true);
}
} }
} }

View File

@@ -32,7 +32,7 @@ namespace Unity.Netcode.Components
private void FixedUpdate() private void FixedUpdate()
{ {
if (NetworkManager.IsListening) if (IsSpawned)
{ {
if (HasAuthority != m_IsAuthority) if (HasAuthority != m_IsAuthority)
{ {
@@ -74,7 +74,6 @@ namespace Unity.Netcode.Components
/// <inheritdoc /> /// <inheritdoc />
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
{ {
m_IsAuthority = false;
UpdateRigidbodyKinematicMode(); UpdateRigidbodyKinematicMode();
} }
} }

View File

@@ -807,7 +807,7 @@ namespace Unity.Netcode.Components
// conditional to users only making transform update changes in FixedUpdate. // conditional to users only making transform update changes in FixedUpdate.
protected virtual void Update() protected virtual void Update()
{ {
if (!NetworkObject.IsSpawned) if (!IsSpawned)
{ {
return; return;
} }

View File

@@ -264,6 +264,28 @@ namespace Unity.Netcode.Editor.CodeGen
}); });
} }
public static void AddWarning(this List<DiagnosticMessage> diagnostics, string message)
{
diagnostics.AddWarning((SequencePoint)null, message);
}
public static void AddWarning(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDefinition, string message)
{
diagnostics.AddWarning(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message);
}
public static void AddWarning(this List<DiagnosticMessage> diagnostics, SequencePoint sequencePoint, string message)
{
diagnostics.Add(new DiagnosticMessage
{
DiagnosticType = DiagnosticType.Warning,
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""),
Line = sequencePoint?.StartLine ?? 0,
Column = sequencePoint?.StartColumn ?? 0,
MessageData = $" - {message}"
});
}
public static void RemoveRecursiveReferences(this ModuleDefinition moduleDefinition) public static void RemoveRecursiveReferences(this ModuleDefinition moduleDefinition)
{ {
// Weird behavior from Cecil: When importing a reference to a specific implementation of a generic // Weird behavior from Cecil: When importing a reference to a specific implementation of a generic

View File

@@ -0,0 +1,167 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
using MethodAttributes = Mono.Cecil.MethodAttributes;
namespace Unity.Netcode.Editor.CodeGen
{
internal sealed class INetworkSerializableILPP : ILPPInterface
{
public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
if (!WillProcess(compiledAssembly))
{
return null;
}
m_Diagnostics.Clear();
// read
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver);
if (assemblyDefinition == null)
{
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
return null;
}
// process
var mainModule = assemblyDefinition.MainModule;
if (mainModule != null)
{
try
{
if (ImportReferences(mainModule))
{
var types = mainModule.GetTypes()
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && t.Resolve().IsValueType)
.ToList();
// process `INetworkMessage` types
if (types.Count == 0)
{
return null;
}
CreateModuleInitializer(assemblyDefinition, types);
}
else
{
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
}
}
catch (Exception e)
{
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
}
}
else
{
m_Diagnostics.AddError($"Cannot get main module from assembly definition: {compiledAssembly.Name}");
}
mainModule.RemoveRecursiveReferences();
// write
var pe = new MemoryStream();
var pdb = new MemoryStream();
var writerParameters = new WriterParameters
{
SymbolWriterProvider = new PortablePdbWriterProvider(),
SymbolStream = pdb,
WriteSymbols = true
};
assemblyDefinition.Write(pe, writerParameters);
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
}
private MethodReference m_InitializeDelegates_MethodRef;
private const string k_InitializeMethodName = nameof(NetworkVariableHelper.InitializeDelegates);
private bool ImportReferences(ModuleDefinition moduleDefinition)
{
var helperType = typeof(NetworkVariableHelper);
foreach (var methodInfo in helperType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
switch (methodInfo.Name)
{
case k_InitializeMethodName:
m_InitializeDelegates_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
}
}
return true;
}
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
{
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
if (staticCtorMethodDef == null)
{
staticCtorMethodDef = new MethodDefinition(
".cctor", // Static Constructor (constant-constructor)
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.Static,
typeDefinition.Module.TypeSystem.Void);
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
typeDefinition.Methods.Add(staticCtorMethodDef);
}
return staticCtorMethodDef;
}
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
// message types in the assembly with MessagingSystem.
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkSerializableTypes)
{
foreach (var typeDefinition in assembly.MainModule.Types)
{
if (typeDefinition.FullName == "<Module>")
{
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
var processor = staticCtorMethodDef.Body.GetILProcessor();
var instructions = new List<Instruction>();
foreach (var type in networkSerializableTypes)
{
var method = new GenericInstanceMethod(m_InitializeDelegates_MethodRef);
method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method));
}
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
break;
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 64a0c1e708fa46a389d64e7b4708e6c7
timeCreated: 1635535237

View File

@@ -312,7 +312,11 @@ namespace Unity.Netcode.Editor.CodeGen
assemblies.Add(m_MainModule.Assembly); assemblies.Add(m_MainModule.Assembly);
foreach (var reference in m_MainModule.AssemblyReferences) foreach (var reference in m_MainModule.AssemblyReferences)
{ {
assemblies.Add(m_AssemblyResolver.Resolve(reference)); var assembly = m_AssemblyResolver.Resolve(reference);
if (assembly != null)
{
assemblies.Add(assembly);
}
} }
var extensionConstructor = var extensionConstructor =
@@ -575,15 +579,23 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
if (method.GenericParameters[0].HasConstraints) if (method.GenericParameters[0].HasConstraints)
{ {
var meetsConstraints = true;
foreach (var constraint in method.GenericParameters[0].Constraints) foreach (var constraint in method.GenericParameters[0].Constraints)
{ {
var resolvedConstraint = constraint.Resolve(); var resolvedConstraint = constraint.Resolve();
if ( if (
(resolvedConstraint.IsInterface && (resolvedConstraint.IsInterface &&
checkType.HasInterface(resolvedConstraint.FullName)) !checkType.HasInterface(resolvedConstraint.FullName))
|| (resolvedConstraint.IsClass && || (resolvedConstraint.IsClass &&
checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))) !checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))
|| (resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
{
meetsConstraints = false;
break;
}
}
if (meetsConstraints)
{ {
var instanceMethod = new GenericInstanceMethod(method); var instanceMethod = new GenericInstanceMethod(method);
instanceMethod.GenericArguments.Add(checkType); instanceMethod.GenericArguments.Add(checkType);
@@ -593,7 +605,6 @@ namespace Unity.Netcode.Editor.CodeGen
} }
} }
} }
}
return null; return null;
} }
@@ -950,17 +961,23 @@ namespace Unity.Netcode.Editor.CodeGen
// writer.WriteValueSafe(param) for value types, OR // writer.WriteValueSafe(param) for value types, OR
// writer.WriteValueSafe(param, -1, 0) for arrays of value types, OR // writer.WriteValueSafe(param, -1, 0) for arrays of value types, OR
// writer.WriteValueSafe(param, false) for strings // writer.WriteValueSafe(param, false) for strings
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
var method = methodRef.Resolve(); var method = methodRef.Resolve();
var checkParameter = method.Parameters[0]; var checkParameter = method.Parameters[0];
var isExtensionMethod = false; var isExtensionMethod = false;
if (checkParameter.ParameterType.Resolve() == if (methodRef.Resolve().DeclaringType != m_FastBufferWriter_TypeRef.Resolve())
m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve())
{ {
isExtensionMethod = true; isExtensionMethod = true;
checkParameter = method.Parameters[1]; checkParameter = method.Parameters[1];
} }
if (checkParameter.IsIn) if (!isExtensionMethod || method.Parameters[0].ParameterType.IsByReference)
{
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
}
else
{
instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx));
}
if (checkParameter.IsIn || checkParameter.IsOut || checkParameter.ParameterType.IsByReference)
{ {
instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1)); instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1));
} }
@@ -1127,6 +1144,11 @@ namespace Unity.Netcode.Editor.CodeGen
nhandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef)); nhandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef));
var processor = nhandler.Body.GetILProcessor(); var processor = nhandler.Body.GetILProcessor();
// begin Try/Catch
var tryStart = processor.Create(OpCodes.Nop);
processor.Append(tryStart);
var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName;
var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership` var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership`
foreach (var attrField in rpcAttribute.Fields) foreach (var attrField in rpcAttribute.Fields)
@@ -1266,7 +1288,18 @@ namespace Unity.Netcode.Editor.CodeGen
if (foundMethodRef) if (foundMethodRef)
{ {
// reader.ReadValueSafe(out localVar); // reader.ReadValueSafe(out localVar);
var checkParameter = methodRef.Resolve().Parameters[0];
var isExtensionMethod = methodRef.Resolve().DeclaringType != m_FastBufferReader_TypeRef.Resolve();
if (!isExtensionMethod || checkParameter.ParameterType.IsByReference)
{
processor.Emit(OpCodes.Ldarga, 1); processor.Emit(OpCodes.Ldarga, 1);
}
else
{
processor.Emit(OpCodes.Ldarg, 1);
}
processor.Emit(OpCodes.Ldloca, localIndex); processor.Emit(OpCodes.Ldloca, localIndex);
if (paramType == typeSystem.String) if (paramType == typeSystem.String)
{ {
@@ -1303,7 +1336,54 @@ namespace Unity.Netcode.Editor.CodeGen
processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None); processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None);
processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef); processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef);
// pull in the Exception Module
var exception = m_MainModule.ImportReference(typeof(Exception));
// Get Exception.ToString()
var exp = m_MainModule.ImportReference(typeof(Exception).GetMethod("ToString", new Type[] { }));
// Get String.Format (This is equivalent to an interpolated string)
var stringFormat = m_MainModule.ImportReference(typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }));
nhandler.Body.Variables.Add(new VariableDefinition(exception));
int exceptionVariableIndex = nhandler.Body.Variables.Count - 1;
//try ends/catch begins
var catchEnds = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Leave, catchEnds);
// Load the Exception onto the stack
var catchStarts = processor.Create(OpCodes.Stloc, exceptionVariableIndex);
processor.Append(catchStarts);
// Load string for the error log that will be shown
processor.Emit(OpCodes.Ldstr, $"Unhandled RPC Exception:\n {{0}}");
processor.Emit(OpCodes.Ldloc, exceptionVariableIndex);
processor.Emit(OpCodes.Callvirt, exp);
processor.Emit(OpCodes.Call, stringFormat);
// Call Debug.LogError
processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef);
// reset NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.None;
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None);
processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef);
// catch ends
processor.Append(catchEnds);
processor.Body.ExceptionHandlers.Add(new ExceptionHandler(ExceptionHandlerType.Catch)
{
CatchType = exception,
TryStart = tryStart,
TryEnd = catchStarts,
HandlerStart = catchStarts,
HandlerEnd = catchEnds
});
processor.Emit(OpCodes.Ret); processor.Emit(OpCodes.Ret);
return nhandler; return nhandler;
} }
} }

View File

@@ -52,6 +52,9 @@ namespace Unity.Netcode.Editor.CodeGen
case nameof(NetworkBehaviour): case nameof(NetworkBehaviour):
ProcessNetworkBehaviour(typeDefinition); ProcessNetworkBehaviour(typeDefinition);
break; break;
case nameof(NetworkVariableHelper):
ProcessNetworkVariableHelper(typeDefinition);
break;
case nameof(__RpcParams): case nameof(__RpcParams):
typeDefinition.IsPublic = true; typeDefinition.IsPublic = true;
break; break;
@@ -100,6 +103,17 @@ namespace Unity.Netcode.Editor.CodeGen
} }
} }
private void ProcessNetworkVariableHelper(TypeDefinition typeDefinition)
{
foreach (var methodDefinition in typeDefinition.Methods)
{
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegates))
{
methodDefinition.IsPublic = true;
}
}
}
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
{ {
foreach (var nestedType in typeDefinition.NestedTypes) foreach (var nestedType in typeDefinition.NestedTypes)

View File

@@ -15,7 +15,6 @@ namespace Unity.Netcode.Editor
private static GUIStyle s_HelpBoxStyle; private static GUIStyle s_HelpBoxStyle;
// Properties // Properties
private SerializedProperty m_DontDestroyOnLoadProperty;
private SerializedProperty m_RunInBackgroundProperty; private SerializedProperty m_RunInBackgroundProperty;
private SerializedProperty m_LogLevelProperty; private SerializedProperty m_LogLevelProperty;
@@ -85,7 +84,6 @@ namespace Unity.Netcode.Editor
m_NetworkManager = (NetworkManager)target; m_NetworkManager = (NetworkManager)target;
// Base properties // Base properties
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground)); m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel)); m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)); m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
@@ -112,7 +110,6 @@ namespace Unity.Netcode.Editor
private void CheckNullProperties() private void CheckNullProperties()
{ {
// Base properties // Base properties
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground)); m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel)); m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)); m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
@@ -223,7 +220,6 @@ namespace Unity.Netcode.Editor
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
{ {
serializedObject.Update(); serializedObject.Update();
EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty);
EditorGUILayout.PropertyField(m_RunInBackgroundProperty); EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
EditorGUILayout.PropertyField(m_LogLevelProperty); EditorGUILayout.PropertyField(m_LogLevelProperty);
EditorGUILayout.Space(); EditorGUILayout.Space();
@@ -363,7 +359,7 @@ namespace Unity.Netcode.Editor
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager."; const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
const string openDocsButtonText = "Open Docs"; const string openDocsButtonText = "Open Docs";
const string dismissButtonText = "Dismiss"; const string dismissButtonText = "Dismiss";
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tutorials/goldenpath_series/goldenpath_foundation_module"; const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tools/install-tools";
const string infoIconName = "console.infoicon"; const string infoIconName = "console.infoicon";
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0) if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)

View File

@@ -0,0 +1,108 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Unity.Netcode.Editor
{
#if UNITY_EDITOR
/// <summary>
/// Specialized editor specific NetworkManager code
/// </summary>
public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper
{
internal static NetworkManagerHelper Singleton;
// This is primarily to handle multiInstance scenarios where more than 1 NetworkManager could exist
private static Dictionary<NetworkManager, Transform> s_LastKnownNetworkManagerParents = new Dictionary<NetworkManager, Transform>();
/// <summary>
/// Initializes the singleton instance and registers for:
/// Hierarchy changed notification: to notify the user when they nest a NetworkManager
/// Play mode state change notification: to capture when entering or exiting play mode (currently only exiting)
/// </summary>
[InitializeOnLoadMethod]
private static void InitializeOnload()
{
Singleton = new NetworkManagerHelper();
NetworkManager.NetworkManagerHelper = Singleton;
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged;
}
private static void EditorApplication_playModeStateChanged(PlayModeStateChange playModeStateChange)
{
switch (playModeStateChange)
{
case PlayModeStateChange.ExitingEditMode:
{
s_LastKnownNetworkManagerParents.Clear();
break;
}
}
}
private static void EditorApplication_hierarchyChanged()
{
var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>();
foreach (var networkManager in allNetworkManagers)
{
networkManager.NetworkManagerCheckForParent();
}
}
/// <summary>
/// Handles notifying the user, via display dialog window, that they have nested a NetworkManager.
/// When in edit mode it provides the option to automatically fix the issue
/// When in play mode it just notifies the user when entering play mode as well as when the user
/// tries to start a network session while a NetworkManager is still nested.
/// </summary>
public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false)
{
var gameObject = networkManager.gameObject;
var transform = networkManager.transform;
var isParented = transform.root != transform;
var message = NetworkManager.GenerateNestedNetworkManagerMessage(transform);
if (s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && !ignoreNetworkManagerCache)
{
// If we have already notified the user, then don't notify them again
if (s_LastKnownNetworkManagerParents[networkManager] == transform.root)
{
return isParented;
}
else // If we are no longer a child, then we can remove ourself from this list
if (transform.root == gameObject.transform)
{
s_LastKnownNetworkManagerParents.Remove(networkManager);
}
}
if (!EditorApplication.isUpdating && isParented)
{
if (!EditorApplication.isPlaying && !editorTest)
{
message += $"Click 'Auto-Fix' to automatically remove it from {transform.root.gameObject.name} or 'Manual-Fix' to fix it yourself in the hierarchy view.";
if (EditorUtility.DisplayDialog("Invalid Nested NetworkManager", message, "Auto-Fix", "Manual-Fix"))
{
transform.parent = null;
isParented = false;
}
}
else
{
Debug.LogError(message);
}
if (!s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && isParented)
{
s_LastKnownNetworkManagerParents.Add(networkManager, networkManager.transform.root);
}
}
return isParented;
}
}
#endif
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 3e168a2bc1a1e2642af0369780fb560c guid: b26b53dc28ae1b5488bbbecc3e499bbc
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -1,7 +1,7 @@
[![Forums](https://img.shields.io/badge/unity--forums-multiplayer-blue)](https://forum.unity.com/forums/multiplayer.26/) [![Discord](https://img.shields.io/discord/449263083769036810.svg?label=discord&logo=discord&color=informational)](https://discord.gg/FM8SE9E) [![Forums](https://img.shields.io/badge/unity--forums-multiplayer-blue)](https://forum.unity.com/forums/multiplayer.26/) [![Discord](https://img.shields.io/discord/449263083769036810.svg?label=discord&logo=discord&color=informational)](https://discord.gg/FM8SE9E)
[![Website](https://img.shields.io/badge/docs-website-informational.svg)](https://docs-multiplayer.unity3d.com/) [![Api](https://img.shields.io/badge/docs-api-informational.svg)](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction) [![Website](https://img.shields.io/badge/docs-website-informational.svg)](https://docs-multiplayer.unity3d.com/) [![Api](https://img.shields.io/badge/docs-api-informational.svg)](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs.unity3d.com/Packages/com.unity.transport@1.0/manual/index.html). Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs-multiplayer.unity3d.com/transport/1.0.0/introduction).
### Getting Started ### Getting Started
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package. Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f2ef964afcae91248b2298b479ed1b53
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,77 +0,0 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Queue with a fixed size
/// </summary>
/// <typeparam name="T">The type of the queue</typeparam>
public sealed class FixedQueue<T>
{
private readonly T[] m_Queue;
private int m_QueueCount = 0;
private int m_QueueStart;
/// <summary>
/// The amount of enqueued objects
/// </summary>
public int Count => m_QueueCount;
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
/// <summary>
/// Creates a new FixedQueue with a given size
/// </summary>
/// <param name="maxSize">The size of the queue</param>
public FixedQueue(int maxSize)
{
m_Queue = new T[maxSize];
m_QueueStart = 0;
}
/// <summary>
/// Enqueues an object
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool Enqueue(T t)
{
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
if (++m_QueueCount > m_Queue.Length)
{
--m_QueueCount;
return true;
}
return false;
}
/// <summary>
/// Dequeues an object
/// </summary>
/// <returns></returns>
public T Dequeue()
{
if (--m_QueueCount == -1)
{
throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
}
T res = m_Queue[m_QueueStart];
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
return res;
}
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
}
}

View File

@@ -141,15 +141,15 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version. /// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
/// </summary> /// </summary>
public bool UseSnapshotDelta { get; } = false; public bool UseSnapshotDelta { get; internal set; } = false;
/// <summary> /// <summary>
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version. /// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
/// </summary> /// </summary>
public bool UseSnapshotSpawn { get; } = false; public bool UseSnapshotSpawn { get; internal set; } = false;
/// <summary> /// <summary>
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU. /// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
/// </summary> /// </summary>
public int SnapshotMaxSpawnUsage { get; } = 1200; public int SnapshotMaxSpawnUsage { get; } = 1000;
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one) public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets) public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
@@ -224,7 +224,7 @@ namespace Unity.Netcode
return m_ConfigHash.Value; return m_ConfigHash.Value;
} }
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp); var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, int.MaxValue);
using (writer) using (writer)
{ {
writer.WriteValueSafe(ProtocolVersion); writer.WriteValueSafe(ProtocolVersion);

View File

@@ -5,6 +5,6 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal static class NetworkConstants internal static class NetworkConstants
{ {
internal const string PROTOCOL_VERSION = "14.0.0"; internal const string PROTOCOL_VERSION = "15.0.0";
} }
} }

View File

@@ -82,7 +82,8 @@ namespace Unity.Netcode
SystemOwner = NetworkManager, SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message. // header information isn't valid since it's not a real message.
// Passing false to canDefer prevents it being accessed. // Passing false to canDefer prevents it being accessed.
Header = new MessageHeader() Header = new MessageHeader(),
SerializedHeaderSize = 0,
}; };
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false); message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
rpcMessageSize = tempBuffer.Length; rpcMessageSize = tempBuffer.Length;
@@ -188,7 +189,8 @@ namespace Unity.Netcode
SystemOwner = NetworkManager, SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message. // header information isn't valid since it's not a real message.
// Passing false to canDefer prevents it being accessed. // Passing false to canDefer prevents it being accessed.
Header = new MessageHeader() Header = new MessageHeader(),
SerializedHeaderSize = 0,
}; };
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false); message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
messageSize = tempBuffer.Length; messageSize = tempBuffer.Length;
@@ -282,10 +284,14 @@ namespace Unity.Netcode
m_NetworkObject = GetComponentInParent<NetworkObject>(); m_NetworkObject = GetComponentInParent<NetworkObject>();
} }
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (m_NetworkObject == null || NetworkManager.Singleton == null ||
(NetworkManager.Singleton != null && !NetworkManager.Singleton.ShutdownInProgress))
{
if (NetworkLog.CurrentLogLevel < LogLevel.Normal)
{ {
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
} }
}
return m_NetworkObject; return m_NetworkObject;
} }

View File

@@ -73,6 +73,9 @@ namespace Unity.Netcode
} }
} }
private bool m_ShuttingDown;
private bool m_StopProcessingMessages;
private class NetworkManagerHooks : INetworkHooks private class NetworkManagerHooks : INetworkHooks
{ {
private NetworkManager m_NetworkManager; private NetworkManager m_NetworkManager;
@@ -116,7 +119,7 @@ namespace Unity.Netcode
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
{ {
return true; return !m_NetworkManager.m_StopProcessingMessages;
} }
public bool OnVerifyCanReceive(ulong senderId, Type messageType) public bool OnVerifyCanReceive(ulong senderId, Type messageType)
@@ -134,7 +137,7 @@ namespace Unity.Netcode
return false; return false;
} }
return true; return !m_NetworkManager.m_StopProcessingMessages;
} }
} }
@@ -149,14 +152,9 @@ namespace Unity.Netcode
public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData) public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
{ {
var sendBuffer = batchData.ToTempByteArray();
var length = batchData.Length; m_NetworkManager.NetworkConfig.NetworkTransport.Send(m_NetworkManager.ClientIdToTransportId(clientId), sendBuffer, delivery);
//TODO: Transport needs to have a way to send it data without copying and allocating here.
var bytes = batchData.ToArray();
var sendBuffer = new ArraySegment<byte>(bytes, 0, length);
m_NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, delivery);
} }
} }
@@ -199,11 +197,6 @@ namespace Unity.Netcode
public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default; public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default;
/// <summary>
/// Gets or sets if the NetworkManager should be marked as DontDestroyOnLoad
/// </summary>
[HideInInspector] public bool DontDestroy = true;
/// <summary> /// <summary>
/// Gets or sets if the application should be set to run in background /// Gets or sets if the application should be set to run in background
/// </summary> /// </summary>
@@ -228,10 +221,12 @@ namespace Unity.Netcode
public NetworkSceneManager SceneManager { get; private set; } public NetworkSceneManager SceneManager { get; private set; }
public readonly ulong ServerClientId = 0;
/// <summary> /// <summary>
/// Gets the networkId of the server /// Gets the networkId of the server
/// </summary> /// </summary>
public ulong ServerClientId => NetworkConfig.NetworkTransport?.ServerClientId ?? private ulong m_ServerTransportId => NetworkConfig.NetworkTransport?.ServerClientId ??
throw new NullReferenceException( throw new NullReferenceException(
$"The transport in the active {nameof(NetworkConfig)} is null"); $"The transport in the active {nameof(NetworkConfig)} is null");
@@ -248,6 +243,10 @@ namespace Unity.Netcode
private Dictionary<ulong, NetworkClient> m_ConnectedClients = new Dictionary<ulong, NetworkClient>(); private Dictionary<ulong, NetworkClient> m_ConnectedClients = new Dictionary<ulong, NetworkClient>();
private ulong m_NextClientId = 1;
private Dictionary<ulong, ulong> m_ClientIdToTransportIdMap = new Dictionary<ulong, ulong>();
private Dictionary<ulong, ulong> m_TransportIdToClientIdMap = new Dictionary<ulong, ulong>();
private List<NetworkClient> m_ConnectedClientsList = new List<NetworkClient>(); private List<NetworkClient> m_ConnectedClientsList = new List<NetworkClient>();
private List<ulong> m_ConnectedClientIds = new List<ulong>(); private List<ulong> m_ConnectedClientIds = new List<ulong>();
@@ -332,6 +331,9 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public bool IsConnectedClient { get; internal set; } public bool IsConnectedClient { get; internal set; }
public bool ShutdownInProgress { get { return m_ShuttingDown; } }
/// <summary> /// <summary>
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects. /// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
/// </summary> /// </summary>
@@ -344,8 +346,6 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public event Action<ulong> OnClientDisconnectCallback = null; public event Action<ulong> OnClientDisconnectCallback = null;
internal void InvokeOnClientDisconnectCallback(ulong clientId) => OnClientDisconnectCallback?.Invoke(clientId);
/// <summary> /// <summary>
/// The callback to invoke once the server is ready /// The callback to invoke once the server is ready
/// </summary> /// </summary>
@@ -485,6 +485,13 @@ namespace Unity.Netcode
private void Initialize(bool server) private void Initialize(bool server)
{ {
// Don't allow the user to start a network session if the NetworkManager is
// still parented under another GameObject
if (NetworkManagerCheckForParent(true))
{
return;
}
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{ {
NetworkLog.LogInfo(nameof(Initialize)); NetworkLog.LogInfo(nameof(Initialize));
@@ -555,8 +562,6 @@ namespace Unity.Netcode
SnapshotSystem = null; SnapshotSystem = null;
} }
SnapshotSystem = new SnapshotSystem(this);
if (server) if (server)
{ {
NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem(); NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
@@ -569,6 +574,8 @@ namespace Unity.Netcode
NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0); NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0);
NetworkTickSystem.Tick += OnNetworkManagerTick; NetworkTickSystem.Tick += OnNetworkManagerTick;
SnapshotSystem = new SnapshotSystem(this, NetworkConfig, NetworkTickSystem);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
// This is used to remove entries not needed or invalid // This is used to remove entries not needed or invalid
@@ -936,11 +943,6 @@ namespace Unity.Netcode
private void OnEnable() private void OnEnable()
{ {
if (DontDestroy)
{
DontDestroyOnLoad(gameObject);
}
if (RunInBackground) if (RunInBackground)
{ {
Application.runInBackground = true; Application.runInBackground = true;
@@ -950,6 +952,11 @@ namespace Unity.Netcode
{ {
SetSingleton(); SetSingleton();
} }
if (!NetworkManagerCheckForParent())
{
DontDestroyOnLoad(gameObject);
}
} }
private void Awake() private void Awake()
@@ -957,6 +964,48 @@ namespace Unity.Netcode
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded; UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
} }
/// <summary>
/// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject
/// </summary>
private void OnTransformParentChanged()
{
NetworkManagerCheckForParent();
}
/// <summary>
/// Determines if the NetworkManager's GameObject is parented under another GameObject and
/// notifies the user that this is not allowed for the NetworkManager.
/// </summary>
internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = false)
{
#if UNITY_EDITOR
var isParented = NetworkManagerHelper.NotifyUserOfNestedNetworkManager(this, ignoreNetworkManagerCache);
#else
var isParented = transform.root != transform;
if (isParented)
{
throw new Exception(GenerateNestedNetworkManagerMessage(transform));
}
#endif
return isParented;
}
static internal string GenerateNestedNetworkManagerMessage(Transform transform)
{
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
}
#if UNITY_EDITOR
static internal INetworkManagerHelper NetworkManagerHelper;
/// <summary>
/// Interface for NetworkManagerHelper
/// </summary>
internal interface INetworkManagerHelper
{
bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false);
}
#endif
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager // 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) private void OnSceneUnloaded(Scene scene)
{ {
@@ -975,7 +1024,7 @@ namespace Unity.Netcode
// Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
private void OnDestroy() private void OnDestroy()
{ {
Shutdown(); ShutdownInternal();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded; UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
@@ -985,17 +1034,40 @@ namespace Unity.Netcode
} }
} }
private void DisconnectRemoteClient(ulong clientId)
{
var transportId = ClientIdToTransportId(clientId);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
}
/// <summary> /// <summary>
/// Globally shuts down the library. /// Globally shuts down the library.
/// Disconnects clients if connected and stops server if running. /// Disconnects clients if connected and stops server if running.
/// </summary> /// </summary>
public void Shutdown() /// <param name="discardMessageQueue">
/// If false, any messages that are currently in the incoming queue will be handled,
/// and any messages in the outgoing queue will be sent, before the shutdown is processed.
/// If true, NetworkManager will shut down immediately, and any unprocessed or unsent messages
/// will be discarded.
/// </param>
public void Shutdown(bool discardMessageQueue = false)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{ {
NetworkLog.LogInfo(nameof(Shutdown)); NetworkLog.LogInfo(nameof(Shutdown));
} }
m_ShuttingDown = true;
m_StopProcessingMessages = discardMessageQueue;
}
internal void ShutdownInternal()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(ShutdownInternal));
}
if (IsServer) if (IsServer)
{ {
// make sure all messages are flushed before transport disconnect clients // make sure all messages are flushed before transport disconnect clients
@@ -1019,7 +1091,7 @@ namespace Unity.Netcode
continue; continue;
} }
NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key); DisconnectRemoteClient(pair.Key);
} }
} }
@@ -1033,7 +1105,7 @@ namespace Unity.Netcode
continue; continue;
} }
NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key); DisconnectRemoteClient(pair.Key);
} }
} }
} }
@@ -1068,11 +1140,15 @@ namespace Unity.Netcode
MessagingSystem = null; MessagingSystem = null;
} }
if (NetworkConfig?.NetworkTransport != null)
{
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll; NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
}
if (SpawnManager != null) if (SpawnManager != null)
{ {
SpawnManager.DestroyNonSceneObjects(); SpawnManager.CleanupAllTriggers();
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects(); SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null; SpawnManager = null;
@@ -1103,7 +1179,12 @@ namespace Unity.Netcode
NetworkConfig?.NetworkTransport?.Shutdown(); NetworkConfig?.NetworkTransport?.Shutdown();
} }
m_ClientIdToTransportIdMap.Clear();
m_TransportIdToClientIdMap.Clear();
IsListening = false; IsListening = false;
m_ShuttingDown = false;
m_StopProcessingMessages = false;
} }
// INetworkUpdateSystem // INetworkUpdateSystem
@@ -1157,6 +1238,11 @@ namespace Unity.Netcode
return; return;
} }
if (m_ShuttingDown && m_StopProcessingMessages)
{
return;
}
// Only update RTT here, server time is updated by time sync messages // Only update RTT here, server time is updated by time sync messages
var reset = NetworkTimeSystem.Advance(Time.deltaTime); var reset = NetworkTimeSystem.Advance(Time.deltaTime);
if (reset) if (reset)
@@ -1172,10 +1258,19 @@ namespace Unity.Netcode
} }
private void OnNetworkPostLateUpdate() private void OnNetworkPostLateUpdate()
{
if (!m_ShuttingDown || !m_StopProcessingMessages)
{ {
MessagingSystem.ProcessSendQueues(); MessagingSystem.ProcessSendQueues();
NetworkMetrics.DispatchFrame(); NetworkMetrics.DispatchFrame();
}
SpawnManager.CleanupStaleTriggers(); SpawnManager.CleanupStaleTriggers();
if (m_ShuttingDown)
{
ShutdownInternal();
}
} }
/// <summary> /// <summary>
@@ -1229,14 +1324,30 @@ namespace Unity.Netcode
} }
} }
private ulong TransportIdToClientId(ulong transportId)
{
return transportId == m_ServerTransportId ? ServerClientId : m_TransportIdToClientIdMap[transportId];
}
private ulong ClientIdToTransportId(ulong clientId)
{
return clientId == ServerClientId ? m_ServerTransportId : m_ClientIdToTransportIdMap[clientId];
}
private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, ArraySegment<byte> payload, float receiveTime) private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, ArraySegment<byte> payload, float receiveTime)
{ {
var transportId = clientId;
switch (networkEvent) switch (networkEvent)
{ {
case NetworkEvent.Connect: case NetworkEvent.Connect:
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportConnect.Begin(); s_TransportConnect.Begin();
#endif #endif
clientId = m_NextClientId++;
m_ClientIdToTransportIdMap[clientId] = transportId;
m_TransportIdToClientIdMap[transportId] = clientId;
MessagingSystem.ClientConnected(clientId); MessagingSystem.ClientConnected(clientId);
if (IsServer) if (IsServer)
{ {
@@ -1275,6 +1386,8 @@ namespace Unity.Netcode
NetworkLog.LogInfo($"Incoming Data From {clientId}: {payload.Count} bytes"); NetworkLog.LogInfo($"Incoming Data From {clientId}: {payload.Count} bytes");
} }
clientId = TransportIdToClientId(clientId);
HandleIncomingData(clientId, payload, receiveTime); HandleIncomingData(clientId, payload, receiveTime);
break; break;
} }
@@ -1282,6 +1395,12 @@ namespace Unity.Netcode
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.Begin(); s_TransportDisconnect.Begin();
#endif #endif
clientId = TransportIdToClientId(clientId);
OnClientDisconnectCallback?.Invoke(clientId);
m_TransportIdToClientIdMap.Remove(transportId);
m_ClientIdToTransportIdMap.Remove(clientId);
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{ {
@@ -1296,9 +1415,6 @@ namespace Unity.Netcode
{ {
Shutdown(); Shutdown();
} }
OnClientDisconnectCallback?.Invoke(clientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End(); s_TransportDisconnect.End();
#endif #endif
@@ -1405,8 +1521,7 @@ namespace Unity.Netcode
} }
OnClientDisconnectFromServer(clientId); OnClientDisconnectFromServer(clientId);
DisconnectRemoteClient(clientId);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(clientId);
} }
private void OnClientDisconnectFromServer(ulong clientId) private void OnClientDisconnectFromServer(ulong clientId)
@@ -1565,6 +1680,7 @@ namespace Unity.Netcode
} }
else // Server just adds itself as an observer to all spawned NetworkObjects else // Server just adds itself as an observer to all spawned NetworkObjects
{ {
LocalClient = client;
SpawnManager.UpdateObservedNetworkObjects(ownerClientId); SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
InvokeOnClientConnectedCallback(ownerClientId); InvokeOnClientConnectedCallback(ownerClientId);
} }
@@ -1580,7 +1696,7 @@ namespace Unity.Netcode
else else
{ {
PendingClients.Remove(ownerClientId); PendingClients.Remove(ownerClientId);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(ownerClientId); DisconnectRemoteClient(ownerClientId);
} }
} }

View File

@@ -62,20 +62,63 @@ namespace Unity.Netcode
internal int TimesWritten; internal int TimesWritten;
} }
internal class ClientData
{
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
{
internal ulong SequenceNumber;
internal ulong ObjectId;
internal int Tick;
}
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
internal int NextDespawnIndex = 0; // same as above, but for despawns.
// by objectId
// which spawns and despawns did this connection ack'ed ?
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
// list of spawn and despawns commands we sent, with sequence number
// need to manage acknowledgements
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
}
internal delegate int MockSendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId);
internal delegate int MockSpawnObject(SnapshotSpawnCommand spawnCommand);
internal delegate int MockDespawnObject(SnapshotDespawnCommand despawnCommand);
// A table of NetworkVariables that constitutes a Snapshot. // A table of NetworkVariables that constitutes a Snapshot.
// Stores serialized NetworkVariables // Stores serialized NetworkVariables
// todo --M1-- // todo --M1--
// The Snapshot will change for M1b with memory management, instead of just FreeMemoryPosition, there will be data structure // The Snapshot will change for M1b with memory management, instead of just FreeMemoryPosition, there will be data structure
// around available buffer, etc. // around available buffer, etc.
internal class Snapshot internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
{ {
// todo --M1-- functionality to grow these will be needed in a later milestone // todo --M1-- functionality to grow these will be needed in a later milestone
private const int k_MaxVariables = 2000; private const int k_MaxVariables = 2000;
private int m_MaxSpawns = 100; internal int SpawnsBufferCount { get; private set; } = 100;
private int m_MaxDespawns = 100; internal int DespawnsBufferCount { get; private set; } = 100;
private const int k_BufferSize = 30000; private const int k_BufferSize = 30000;
private NetworkManager m_NetworkManager = default;
// by clientId
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
private bool m_UseSnapshotDelta;
private bool m_UseSnapshotSpawn;
private int m_SnapshotMaxSpawnUsage;
private NetworkTickSystem m_NetworkTickSystem;
private int m_CurrentTick = NetworkTickSystem.NoTick;
internal byte[] MainBuffer = new byte[k_BufferSize]; // buffer holding a snapshot in memory internal byte[] MainBuffer = new byte[k_BufferSize]; // buffer holding a snapshot in memory
internal byte[] RecvBuffer = new byte[k_BufferSize]; // buffer holding the received snapshot message internal byte[] RecvBuffer = new byte[k_BufferSize]; // buffer holding the received snapshot message
@@ -90,23 +133,17 @@ namespace Unity.Netcode
internal SnapshotDespawnCommand[] Despawns; internal SnapshotDespawnCommand[] Despawns;
internal int NumDespawns = 0; internal int NumDespawns = 0;
internal NetworkManager NetworkManager;
// indexed by ObjectId // indexed by ObjectId
internal Dictionary<ulong, int> TickAppliedSpawn = new Dictionary<ulong, int>(); internal Dictionary<ulong, int> TickAppliedSpawn = new Dictionary<ulong, int>();
internal Dictionary<ulong, int> TickAppliedDespawn = new Dictionary<ulong, int>(); internal Dictionary<ulong, int> TickAppliedDespawn = new Dictionary<ulong, int>();
/// <summary> internal bool IsServer { get; set; }
/// Constructor internal bool IsConnectedClient { get; set; }
/// Allocated a MemoryStream to be reused for this Snapshot internal ulong ServerClientId { get; set; }
/// </summary> internal List<ulong> ConnectedClientsId { get; } = new List<ulong>();
internal Snapshot() internal MockSendMessage MockSendMessage { get; set; }
{ internal MockSpawnObject MockSpawnObject { get; set; }
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used internal MockDespawnObject MockDespawnObject { get; set; }
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
Spawns = new SnapshotSpawnCommand[m_MaxSpawns];
Despawns = new SnapshotDespawnCommand[m_MaxDespawns];
}
internal void Clear() internal void Clear()
{ {
@@ -156,15 +193,15 @@ namespace Unity.Netcode
List<ulong> clientList; List<ulong> clientList;
clientList = new List<ulong>(); clientList = new List<ulong>();
if (!NetworkManager.IsServer) if (!IsServer)
{ {
clientList.Add(NetworkManager.ServerClientId); clientList.Add(m_NetworkManager.ServerClientId);
} }
else else
{ {
foreach (var clientId in NetworkManager.ConnectedClientsIds) foreach (var clientId in ConnectedClientsId)
{ {
if (clientId != NetworkManager.ServerClientId) if (clientId != m_NetworkManager.ServerClientId)
{ {
clientList.Add(clientId); clientList.Add(clientId);
} }
@@ -176,14 +213,14 @@ namespace Unity.Netcode
internal void AddSpawn(SnapshotSpawnCommand command) internal void AddSpawn(SnapshotSpawnCommand command)
{ {
if (NumSpawns >= m_MaxSpawns) if (NumSpawns >= SpawnsBufferCount)
{ {
Array.Resize(ref Spawns, 2 * m_MaxSpawns); Array.Resize(ref Spawns, 2 * SpawnsBufferCount);
m_MaxSpawns = m_MaxSpawns * 2; SpawnsBufferCount = SpawnsBufferCount * 2;
// Debug.Log($"[JEFF] spawn size is now {m_MaxSpawns}"); // Debug.Log($"[JEFF] spawn size is now {m_MaxSpawns}");
} }
if (NumSpawns < m_MaxSpawns) if (NumSpawns < SpawnsBufferCount)
{ {
if (command.TargetClientIds == default) if (command.TargetClientIds == default)
{ {
@@ -208,14 +245,14 @@ namespace Unity.Netcode
internal void AddDespawn(SnapshotDespawnCommand command) internal void AddDespawn(SnapshotDespawnCommand command)
{ {
if (NumDespawns >= m_MaxDespawns) if (NumDespawns >= DespawnsBufferCount)
{ {
Array.Resize(ref Despawns, 2 * m_MaxDespawns); Array.Resize(ref Despawns, 2 * DespawnsBufferCount);
m_MaxDespawns = m_MaxDespawns * 2; DespawnsBufferCount = DespawnsBufferCount * 2;
// Debug.Log($"[JEFF] despawn size is now {m_MaxDespawns}"); // Debug.Log($"[JEFF] despawn size is now {m_MaxDespawns}");
} }
if (NumDespawns < m_MaxDespawns) if (NumDespawns < DespawnsBufferCount)
{ {
if (command.TargetClientIds == default) if (command.TargetClientIds == default)
{ {
@@ -229,6 +266,17 @@ namespace Unity.Netcode
} }
} }
internal void ReduceBufferUsage()
{
var count = Math.Max(1, NumDespawns);
Array.Resize(ref Despawns, count);
DespawnsBufferCount = count;
count = Math.Max(1, NumSpawns);
Array.Resize(ref Spawns, count);
SpawnsBufferCount = count;
}
internal ClientData.SentSpawn GetSpawnData(in ClientData clientData, in SnapshotSpawnCommand spawn, out SnapshotDataMessage.SpawnData data) internal ClientData.SentSpawn GetSpawnData(in ClientData clientData, in SnapshotSpawnCommand spawn, out SnapshotDataMessage.SpawnData data)
{ {
// remember which spawn we sent this connection with which sequence number // remember which spawn we sent this connection with which sequence number
@@ -417,7 +465,54 @@ namespace Unity.Netcode
} }
} }
internal void ReadSpawns(in SnapshotDataMessage message) internal void SpawnObject(SnapshotSpawnCommand spawnCommand, ulong srcClientId)
{
if (m_NetworkManager)
{
NetworkObject networkObject;
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId)
{
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition,
spawnCommand.ObjectRotation);
}
else
{
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition,
spawnCommand.ObjectRotation);
}
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId,
true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
//todo: discuss with tools how to report shared bytes
m_NetworkManager.NetworkMetrics.TrackObjectSpawnReceived(srcClientId, networkObject, 8);
}
else
{
MockSpawnObject(spawnCommand);
}
}
internal void DespawnObject(SnapshotDespawnCommand despawnCommand, ulong srcClientId)
{
if (m_NetworkManager)
{
m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId,
out NetworkObject networkObject);
m_NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
//todo: discuss with tools how to report shared bytes
m_NetworkManager.NetworkMetrics.TrackObjectDestroyReceived(srcClientId, networkObject, 8);
}
else
{
MockDespawnObject(despawnCommand);
}
}
internal void ReadSpawns(in SnapshotDataMessage message, ulong srcClientId)
{ {
SnapshotSpawnCommand spawnCommand; SnapshotSpawnCommand spawnCommand;
SnapshotDespawnCommand despawnCommand; SnapshotDespawnCommand despawnCommand;
@@ -436,16 +531,7 @@ namespace Unity.Netcode
// Debug.Log($"[Spawn] {spawnCommand.NetworkObjectId} {spawnCommand.TickWritten}"); // Debug.Log($"[Spawn] {spawnCommand.NetworkObjectId} {spawnCommand.TickWritten}");
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId) SpawnObject(spawnCommand, srcClientId);
{
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
}
else
{
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
}
} }
for (var i = 0; i < message.Despawns.Length; i++) for (var i = 0; i < message.Despawns.Length; i++)
{ {
@@ -461,10 +547,7 @@ namespace Unity.Netcode
// Debug.Log($"[DeSpawn] {despawnCommand.NetworkObjectId} {despawnCommand.TickWritten}"); // Debug.Log($"[DeSpawn] {despawnCommand.NetworkObjectId} {despawnCommand.TickWritten}");
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId, DespawnObject(despawnCommand, srcClientId);
out NetworkObject networkObject);
NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
} }
} }
@@ -569,7 +652,7 @@ namespace Unity.Netcode
/// <param name="key">The key to search for</param> /// <param name="key">The key to search for</param>
private NetworkVariableBase FindNetworkVar(VariableKey key) private NetworkVariableBase FindNetworkVar(VariableKey key)
{ {
var spawnedObjects = NetworkManager.SpawnManager.SpawnedObjects; var spawnedObjects = m_NetworkManager.SpawnManager.SpawnedObjects;
if (spawnedObjects.ContainsKey(key.NetworkObjectId)) if (spawnedObjects.ContainsKey(key.NetworkObjectId))
{ {
@@ -580,61 +663,49 @@ namespace Unity.Netcode
return null; return null;
} }
}
internal class ClientData
{
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
{
internal ulong SequenceNumber;
internal ulong ObjectId;
internal int Tick;
}
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
internal int NextDespawnIndex = 0; // same as above, but for despawns.
// by objectId
// which spawns and despawns did this connection ack'ed ?
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
// list of spawn and despawns commands we sent, with sequence number
// need to manage acknowledgements
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
}
internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
{
// temporary, debugging sentinels
internal const ushort SentinelBefore = 0x4246;
internal const ushort SentinelAfter = 0x89CE;
private NetworkManager m_NetworkManager = default;
private Snapshot m_Snapshot = default;
// by clientId
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
private int m_CurrentTick = NetworkTickSystem.NoTick;
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
/// Registers the snapshot system for early updates, keeps reference to the NetworkManager /// Registers the snapshot system for early updates, keeps reference to the NetworkManager
internal SnapshotSystem(NetworkManager networkManager) internal SnapshotSystem(NetworkManager networkManager, NetworkConfig config, NetworkTickSystem networkTickSystem)
{ {
m_Snapshot = new Snapshot();
m_NetworkManager = networkManager; m_NetworkManager = networkManager;
m_Snapshot.NetworkManager = networkManager; m_NetworkTickSystem = networkTickSystem;
m_UseSnapshotDelta = config.UseSnapshotDelta;
m_UseSnapshotSpawn = config.UseSnapshotSpawn;
m_SnapshotMaxSpawnUsage = config.SnapshotMaxSpawnUsage;
UpdateClientServerData();
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate); this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
Spawns = new SnapshotSpawnCommand[SpawnsBufferCount];
Despawns = new SnapshotDespawnCommand[DespawnsBufferCount];
}
// since we don't want to access the NetworkManager directly, we refresh those values on Update
internal void UpdateClientServerData()
{
if (m_NetworkManager)
{
IsServer = m_NetworkManager.IsServer;
IsConnectedClient = m_NetworkManager.IsConnectedClient;
ServerClientId = m_NetworkManager.ServerClientId;
// todo: This is extremely inefficient. What is the efficient and idiomatic way ?
ConnectedClientsId.Clear();
if (IsServer)
{
foreach (var id in m_NetworkManager.ConnectedClientsIds)
{
ConnectedClientsId.Add(id);
}
}
}
} }
internal ConnectionRtt GetConnectionRtt(ulong clientId) internal ConnectionRtt GetConnectionRtt(ulong clientId)
@@ -658,34 +729,36 @@ namespace Unity.Netcode
public void NetworkUpdate(NetworkUpdateStage updateStage) public void NetworkUpdate(NetworkUpdateStage updateStage)
{ {
if (!m_NetworkManager.NetworkConfig.UseSnapshotDelta && !m_NetworkManager.NetworkConfig.UseSnapshotSpawn) if (!m_UseSnapshotDelta && !m_UseSnapshotSpawn)
{ {
return; return;
} }
if (updateStage == NetworkUpdateStage.EarlyUpdate) if (updateStage == NetworkUpdateStage.EarlyUpdate)
{ {
var tick = m_NetworkManager.NetworkTickSystem.LocalTime.Tick; UpdateClientServerData();
var tick = m_NetworkTickSystem.LocalTime.Tick;
if (tick != m_CurrentTick) if (tick != m_CurrentTick)
{ {
m_CurrentTick = tick; m_CurrentTick = tick;
if (m_NetworkManager.IsServer) if (IsServer)
{ {
for (int i = 0; i < m_NetworkManager.ConnectedClientsList.Count; i++) for (int i = 0; i < ConnectedClientsId.Count; i++)
{ {
var clientId = m_NetworkManager.ConnectedClientsList[i].ClientId; var clientId = ConnectedClientsId[i];
// don't send to ourselves // don't send to ourselves
if (clientId != m_NetworkManager.ServerClientId) if (clientId != ServerClientId)
{ {
SendSnapshot(clientId); SendSnapshot(clientId);
} }
} }
} }
else if (m_NetworkManager.IsConnectedClient) else if (IsConnectedClient)
{ {
SendSnapshot(m_NetworkManager.ServerClientId); SendSnapshot(ServerClientId);
} }
} }
@@ -721,12 +794,12 @@ namespace Unity.Netcode
{ {
CurrentTick = m_CurrentTick, CurrentTick = m_CurrentTick,
Sequence = sequence, Sequence = sequence,
Range = (ushort)m_Snapshot.Allocator.Range, Range = (ushort)Allocator.Range,
// todo --M1-- // todo --M1--
// this sends the whole buffer // this sends the whole buffer
// we'll need to build a per-client list // we'll need to build a per-client list
SendMainBuffer = m_Snapshot.MainBuffer, SendMainBuffer = MainBuffer,
Ack = new SnapshotDataMessage.AckData Ack = new SnapshotDataMessage.AckData
{ {
@@ -740,7 +813,14 @@ namespace Unity.Netcode
WriteIndex(ref message); WriteIndex(ref message);
WriteSpawns(ref message, clientId); WriteSpawns(ref message, clientId);
if (m_NetworkManager)
{
m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId); m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId);
}
else
{
MockSendMessage(message, NetworkDelivery.Unreliable, clientId);
}
m_ClientData[clientId].LastReceivedSequence = 0; m_ClientData[clientId].LastReceivedSequence = 0;
@@ -791,51 +871,51 @@ namespace Unity.Netcode
ClientData clientData = m_ClientData[clientId]; ClientData clientData = m_ClientData[clientId];
// this is needed because spawns being removed may have reduce the size below LRU position // this is needed because spawns being removed may have reduce the size below LRU position
if (m_Snapshot.NumSpawns > 0) if (NumSpawns > 0)
{ {
clientData.NextSpawnIndex %= m_Snapshot.NumSpawns; clientData.NextSpawnIndex %= NumSpawns;
} }
else else
{ {
clientData.NextSpawnIndex = 0; clientData.NextSpawnIndex = 0;
} }
if (m_Snapshot.NumDespawns > 0) if (NumDespawns > 0)
{ {
clientData.NextDespawnIndex %= m_Snapshot.NumDespawns; clientData.NextDespawnIndex %= NumDespawns;
} }
else else
{ {
clientData.NextDespawnIndex = 0; clientData.NextDespawnIndex = 0;
} }
message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(m_Snapshot.NumSpawns, Allocator.TempJob); message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(NumSpawns, Collections.Allocator.TempJob);
message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(m_Snapshot.NumDespawns, Allocator.TempJob); message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(NumDespawns, Collections.Allocator.TempJob);
var spawnUsage = 0; var spawnUsage = 0;
for (var j = 0; j < m_Snapshot.NumSpawns && !overSize; j++) for (var j = 0; j < NumSpawns && !overSize; j++)
{ {
var index = clientData.NextSpawnIndex; var index = clientData.NextSpawnIndex;
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns // todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(m_Snapshot.Spawns[index])*/) if (Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(Spawns[index])*/)
{ {
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.SpawnData>(); spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.SpawnData>();
// limit spawn sizes, compare current pos to very first position we wrote to // limit spawn sizes, compare current pos to very first position we wrote to
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage) if (spawnUsage > m_SnapshotMaxSpawnUsage)
{ {
overSize = true; overSize = true;
break; break;
} }
var sentSpawn = m_Snapshot.GetSpawnData(clientData, in m_Snapshot.Spawns[index], out var spawn); var sentSpawn = GetSpawnData(clientData, in Spawns[index], out var spawn);
message.Spawns.Add(spawn); message.Spawns.Add(spawn);
m_Snapshot.Spawns[index].TimesWritten++; Spawns[index].TimesWritten++;
clientData.SentSpawns.Add(sentSpawn); clientData.SentSpawns.Add(sentSpawn);
spawnWritten++; spawnWritten++;
} }
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % m_Snapshot.NumSpawns; clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % NumSpawns;
} }
// even though we might have a spawn we could not fit, it's possible despawns will fit (they're smaller) // even though we might have a spawn we could not fit, it's possible despawns will fit (they're smaller)
@@ -846,28 +926,28 @@ namespace Unity.Netcode
// As-is it is overly restrictive but allows us to go forward without the spawn/despawn dependency check // As-is it is overly restrictive but allows us to go forward without the spawn/despawn dependency check
// overSize = false; // overSize = false;
for (var j = 0; j < m_Snapshot.NumDespawns && !overSize; j++) for (var j = 0; j < NumDespawns && !overSize; j++)
{ {
var index = clientData.NextDespawnIndex; var index = clientData.NextDespawnIndex;
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns // todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(m_Snapshot.Despawns[index])*/) if (Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(Despawns[index])*/)
{ {
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.DespawnData>(); spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.DespawnData>();
// limit spawn sizes, compare current pos to very first position we wrote to // limit spawn sizes, compare current pos to very first position we wrote to
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage) if (spawnUsage > m_SnapshotMaxSpawnUsage)
{ {
overSize = true; overSize = true;
break; break;
} }
var sentDespawn = m_Snapshot.GetDespawnData(clientData, in m_Snapshot.Despawns[index], out var despawn); var sentDespawn = GetDespawnData(clientData, in Despawns[index], out var despawn);
message.Despawns.Add(despawn); message.Despawns.Add(despawn);
m_Snapshot.Despawns[index].TimesWritten++; Despawns[index].TimesWritten++;
clientData.SentSpawns.Add(sentDespawn); clientData.SentSpawns.Add(sentDespawn);
despawnWritten++; despawnWritten++;
} }
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % m_Snapshot.NumDespawns; clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % NumDespawns;
} }
} }
@@ -877,10 +957,10 @@ namespace Unity.Netcode
/// <param name="message">The message to write the index to</param> /// <param name="message">The message to write the index to</param>
private void WriteIndex(ref SnapshotDataMessage message) private void WriteIndex(ref SnapshotDataMessage message)
{ {
message.Entries = new NativeList<SnapshotDataMessage.EntryData>(m_Snapshot.LastEntry, Allocator.TempJob); message.Entries = new NativeList<SnapshotDataMessage.EntryData>(LastEntry, Collections.Allocator.TempJob);
for (var i = 0; i < m_Snapshot.LastEntry; i++) for (var i = 0; i < LastEntry; i++)
{ {
var entryMeta = m_Snapshot.Entries[i]; var entryMeta = Entries[i];
var entry = entryMeta.Key; var entry = entryMeta.Key;
message.Entries.Add(new SnapshotDataMessage.EntryData message.Entries.Add(new SnapshotDataMessage.EntryData
{ {
@@ -897,7 +977,7 @@ namespace Unity.Netcode
internal void Spawn(SnapshotSpawnCommand command) internal void Spawn(SnapshotSpawnCommand command)
{ {
command.TickWritten = m_CurrentTick; command.TickWritten = m_CurrentTick;
m_Snapshot.AddSpawn(command); AddSpawn(command);
// Debug.Log($"[Spawn] {command.NetworkObjectId} {command.TickWritten}"); // Debug.Log($"[Spawn] {command.NetworkObjectId} {command.TickWritten}");
} }
@@ -905,7 +985,7 @@ namespace Unity.Netcode
internal void Despawn(SnapshotDespawnCommand command) internal void Despawn(SnapshotDespawnCommand command)
{ {
command.TickWritten = m_CurrentTick; command.TickWritten = m_CurrentTick;
m_Snapshot.AddDespawn(command); AddDespawn(command);
// Debug.Log($"[DeSpawn] {command.NetworkObjectId} {command.TickWritten}"); // Debug.Log($"[DeSpawn] {command.NetworkObjectId} {command.TickWritten}");
} }
@@ -922,35 +1002,35 @@ namespace Unity.Netcode
k.NetworkObjectId = networkObjectId; k.NetworkObjectId = networkObjectId;
k.BehaviourIndex = (ushort)behaviourIndex; k.BehaviourIndex = (ushort)behaviourIndex;
k.VariableIndex = (ushort)variableIndex; k.VariableIndex = (ushort)variableIndex;
k.TickWritten = m_NetworkManager.NetworkTickSystem.LocalTime.Tick; k.TickWritten = m_NetworkTickSystem.LocalTime.Tick;
int pos = m_Snapshot.Find(k); int pos = Find(k);
if (pos == Entry.NotFound) if (pos == Entry.NotFound)
{ {
pos = m_Snapshot.AddEntry(k); pos = AddEntry(k);
} }
m_Snapshot.Entries[pos].Key.TickWritten = k.TickWritten; Entries[pos].Key.TickWritten = k.TickWritten;
WriteVariableToSnapshot(m_Snapshot, networkVariable, pos); WriteVariable(networkVariable, pos);
} }
private unsafe void WriteVariableToSnapshot(Snapshot snapshot, NetworkVariableBase networkVariable, int index) private unsafe void WriteVariable(NetworkVariableBase networkVariable, int index)
{ {
// write var into buffer, possibly adjusting entry's position and Length // write var into buffer, possibly adjusting entry's position and Length
var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp); var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Collections.Allocator.Temp);
using (varBuffer) using (varBuffer)
{ {
networkVariable.WriteDelta(varBuffer); networkVariable.WriteDelta(varBuffer);
if (varBuffer.Length > snapshot.Entries[index].Length) if (varBuffer.Length > Entries[index].Length)
{ {
// allocate this Entry's buffer // allocate this Entry's buffer
snapshot.AllocateEntry(ref snapshot.Entries[index], index, (int)varBuffer.Length); AllocateEntry(ref Entries[index], index, (int)varBuffer.Length);
} }
fixed (byte* buffer = snapshot.MainBuffer) fixed (byte* buffer = MainBuffer)
{ {
UnsafeUtility.MemCpy(buffer + snapshot.Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length); UnsafeUtility.MemCpy(buffer + Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length);
} }
} }
} }
@@ -1009,10 +1089,10 @@ namespace Unity.Netcode
// without this, we incur extra retransmit, not a catastrophic failure // without this, we incur extra retransmit, not a catastrophic failure
} }
m_Snapshot.ReadBuffer(message); ReadBuffer(message);
m_Snapshot.ReadIndex(message); ReadIndex(message);
m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId)); ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId));
m_Snapshot.ReadSpawns(message); ReadSpawns(message, clientId);
} }
// todo --M1-- // todo --M1--
@@ -1024,14 +1104,14 @@ namespace Unity.Netcode
table += $"We're clientId {m_NetworkManager.LocalClientId}\n"; table += $"We're clientId {m_NetworkManager.LocalClientId}\n";
table += "=== Variables ===\n"; table += "=== Variables ===\n";
for (int i = 0; i < m_Snapshot.LastEntry; i++) for (int i = 0; i < LastEntry; i++)
{ {
table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", m_Snapshot.Entries[i].Key.NetworkObjectId, m_Snapshot.Entries[i].Key.BehaviourIndex, table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", Entries[i].Key.NetworkObjectId, Entries[i].Key.BehaviourIndex,
m_Snapshot.Entries[i].Key.VariableIndex, m_Snapshot.Entries[i].Position, m_Snapshot.Entries[i].Position + m_Snapshot.Entries[i].Length, m_Snapshot.Entries[i].Key.TickWritten); Entries[i].Key.VariableIndex, Entries[i].Position, Entries[i].Position + Entries[i].Length, Entries[i].Key.TickWritten);
for (int j = 0; j < m_Snapshot.Entries[i].Length && j < 4; j++) for (int j = 0; j < Entries[i].Length && j < 4; j++)
{ {
table += m_Snapshot.MainBuffer[m_Snapshot.Entries[i].Position + j].ToString("X2") + " "; table += MainBuffer[Entries[i].Position + j].ToString("X2") + " ";
} }
table += "\n"; table += "\n";
@@ -1039,14 +1119,14 @@ namespace Unity.Netcode
table += "=== Spawns ===\n"; table += "=== Spawns ===\n";
for (int i = 0; i < m_Snapshot.NumSpawns; i++) for (int i = 0; i < NumSpawns; i++)
{ {
string targets = ""; string targets = "";
foreach (var target in m_Snapshot.Spawns[i].TargetClientIds) foreach (var target in Spawns[i].TargetClientIds)
{ {
targets += target.ToString() + ", "; targets += target.ToString() + ", ";
} }
table += $"Spawn Object Id {m_Snapshot.Spawns[i].NetworkObjectId}, Tick {m_Snapshot.Spawns[i].TickWritten}, Target {targets}\n"; table += $"Spawn Object Id {Spawns[i].NetworkObjectId}, Tick {Spawns[i].TickWritten}, Target {targets}\n";
} }
table += $"=== RTTs ===\n"; table += $"=== RTTs ===\n";

View File

@@ -28,7 +28,7 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public event UnnamedMessageDelegate OnUnnamedMessage; public event UnnamedMessageDelegate OnUnnamedMessage;
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader) internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize)
{ {
if (OnUnnamedMessage != null) if (OnUnnamedMessage != null)
{ {
@@ -40,7 +40,7 @@ namespace Unity.Netcode
((UnnamedMessageDelegate)handler).Invoke(clientId, reader); ((UnnamedMessageDelegate)handler).Invoke(clientId, reader);
} }
} }
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length); m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + serializedHeaderSize);
} }
/// <summary> /// <summary>
@@ -53,7 +53,6 @@ namespace Unity.Netcode
SendUnnamedMessage(m_NetworkManager.ConnectedClientsIds, messageBuffer, networkDelivery); SendUnnamedMessage(m_NetworkManager.ConnectedClientsIds, messageBuffer, networkDelivery);
} }
/// <summary> /// <summary>
/// Sends unnamed message to a list of clients /// Sends unnamed message to a list of clients
/// </summary> /// </summary>
@@ -116,9 +115,9 @@ namespace Unity.Netcode
private Dictionary<ulong, string> m_MessageHandlerNameLookup32 = new Dictionary<ulong, string>(); private Dictionary<ulong, string> m_MessageHandlerNameLookup32 = new Dictionary<ulong, string>();
private Dictionary<ulong, string> m_MessageHandlerNameLookup64 = new Dictionary<ulong, string>(); private Dictionary<ulong, string> m_MessageHandlerNameLookup64 = new Dictionary<ulong, string>();
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader) internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader, int serializedHeaderSize)
{ {
var bytesCount = reader.Length; var bytesCount = reader.Length + serializedHeaderSize;
if (m_NetworkManager == null) if (m_NetworkManager == null)
{ {

View File

@@ -11,11 +11,12 @@ namespace Unity.Netcode
/// unchanged - if new messages are added or messages are removed, MessageType assignments may be /// unchanged - if new messages are added or messages are removed, MessageType assignments may be
/// calculated differently. /// calculated differently.
/// </summary> /// </summary>
public byte MessageType; public uint MessageType;
/// <summary> /// <summary>
/// The total size of the message, NOT including the header. /// The total size of the message, NOT including the header.
/// Stored as a uint to avoid zig-zag encoding, but capped at int.MaxValue.
/// </summary> /// </summary>
public ushort MessageSize; public uint MessageSize;
} }
} }

View File

@@ -16,7 +16,7 @@ namespace Unity.Netcode
var message = new NamedMessage(); var message = new NamedMessage();
reader.ReadValueSafe(out message.Hash); reader.ReadValueSafe(out message.Hash);
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader); ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader, context.SerializedHeaderSize);
} }
} }
} }

View File

@@ -104,7 +104,6 @@ namespace Unity.Netcode
public static unsafe void Receive(FastBufferReader reader, in NetworkContext context) public static unsafe void Receive(FastBufferReader reader, in NetworkContext context)
{ {
var networkManager = (NetworkManager)context.SystemOwner;
var message = new SnapshotDataMessage(); var message = new SnapshotDataMessage();
if (!reader.TryBeginRead( if (!reader.TryBeginRead(
FastBufferWriter.GetWriteSize(message.CurrentTick) + FastBufferWriter.GetWriteSize(message.CurrentTick) +
@@ -142,12 +141,16 @@ namespace Unity.Netcode
using (message.Spawns) using (message.Spawns)
using (message.Despawns) using (message.Despawns)
{ {
message.Handle(context.SenderId, networkManager); message.Handle(context.SenderId, context.SystemOwner);
} }
} }
public void Handle(ulong senderId, NetworkManager networkManager) public void Handle(ulong senderId, object systemOwner)
{ {
if (systemOwner is NetworkManager)
{
var networkManager = (NetworkManager)systemOwner;
// todo: temporary hack around bug // todo: temporary hack around bug
if (!networkManager.IsServer) if (!networkManager.IsServer)
{ {
@@ -157,5 +160,13 @@ namespace Unity.Netcode
var snapshotSystem = networkManager.SnapshotSystem; var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this); snapshotSystem.HandleSnapshot(senderId, this);
} }
else
{
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
var snapshotSystem = ownerData.Item1;
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
return;
}
}
} }
} }

View File

@@ -11,7 +11,7 @@ namespace Unity.Netcode
public static void Receive(FastBufferReader reader, in NetworkContext context) public static void Receive(FastBufferReader reader, in NetworkContext context)
{ {
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader); ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader, context.SerializedHeaderSize);
} }
} }
} }

View File

@@ -23,6 +23,7 @@ namespace Unity.Netcode
public MessageHeader Header; public MessageHeader Header;
public ulong SenderId; public ulong SenderId;
public float Timestamp; public float Timestamp;
public int MessageHeaderSerializedSize;
} }
private struct SendQueueItem private struct SendQueueItem
@@ -46,27 +47,27 @@ namespace Unity.Netcode
private MessageHandler[] m_MessageHandlers = new MessageHandler[255]; private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
private Type[] m_ReverseTypeMap = new Type[255]; private Type[] m_ReverseTypeMap = new Type[255];
private Dictionary<Type, byte> m_MessageTypes = new Dictionary<Type, byte>(); private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>(); private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>(); private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
private byte m_HighMessageType; private uint m_HighMessageType;
private object m_Owner; private object m_Owner;
private IMessageSender m_MessageSender; private IMessageSender m_MessageSender;
private bool m_Disposed; private bool m_Disposed;
internal Type[] MessageTypes => m_ReverseTypeMap; internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers; internal MessageHandler[] MessageHandlers => m_MessageHandlers;
internal int MessageHandlerCount => m_HighMessageType; internal uint MessageHandlerCount => m_HighMessageType;
internal byte GetMessageType(Type t) internal uint GetMessageType(Type t)
{ {
return m_MessageTypes[t]; return m_MessageTypes[t];
} }
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300; public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = 64000; public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
internal struct MessageWithHandler internal struct MessageWithHandler
{ {
@@ -100,7 +101,7 @@ namespace Unity.Netcode
} }
} }
public void Dispose() public unsafe void Dispose()
{ {
if (m_Disposed) if (m_Disposed)
{ {
@@ -113,6 +114,14 @@ namespace Unity.Netcode
{ {
CleanupDisconnectedClient(kvp.Key); CleanupDisconnectedClient(kvp.Key);
} }
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
{
// Avoid copies...
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(queueIndex);
item.Reader.Dispose();
}
m_IncomingMessageQueue.Dispose(); m_IncomingMessageQueue.Dispose();
m_Disposed = true; m_Disposed = true;
} }
@@ -141,7 +150,7 @@ namespace Unity.Netcode
fixed (byte* nativeData = data.Array) fixed (byte* nativeData = data.Array)
{ {
var batchReader = var batchReader =
new FastBufferReader(nativeData, Allocator.None, data.Count, data.Offset); new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
if (!batchReader.TryBeginRead(sizeof(BatchHeader))) if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
{ {
NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it."); NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it.");
@@ -157,14 +166,23 @@ namespace Unity.Netcode
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx) for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
{ {
if (!batchReader.TryBeginRead(sizeof(MessageHeader)))
var messageHeader = new MessageHeader();
var position = batchReader.Position;
try
{
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageType);
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageSize);
}
catch (OverflowException)
{ {
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!"); NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
return; throw;
} }
batchReader.ReadValue(out MessageHeader messageHeader);
if (!batchReader.TryBeginRead(messageHeader.MessageSize)) var receivedHeaderSize = batchReader.Position - position;
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
{ {
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!"); NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
return; return;
@@ -177,9 +195,10 @@ namespace Unity.Netcode
// Copy the data for this message into a new FastBufferReader that owns that memory. // 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, // 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. // so we must move it to memory we do own.
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, messageHeader.MessageSize) Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, (int)messageHeader.MessageSize),
MessageHeaderSerializedSize = receivedHeaderSize,
}); });
batchReader.Seek(batchReader.Position + messageHeader.MessageSize); batchReader.Seek(batchReader.Position + (int)messageHeader.MessageSize);
} }
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
@@ -202,7 +221,7 @@ namespace Unity.Netcode
return true; return true;
} }
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp) public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{ {
if (header.MessageType >= m_HighMessageType) if (header.MessageType >= m_HighMessageType)
{ {
@@ -215,8 +234,10 @@ namespace Unity.Netcode
SystemOwner = m_Owner, SystemOwner = m_Owner,
SenderId = senderId, SenderId = senderId,
Timestamp = timestamp, Timestamp = timestamp,
Header = header Header = header,
SerializedHeaderSize = serializedHeaderSize,
}; };
var type = m_ReverseTypeMap[header.MessageType]; var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type)) if (!CanReceive(senderId, type))
{ {
@@ -226,8 +247,9 @@ namespace Unity.Netcode
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length); m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
} }
var handler = m_MessageHandlers[header.MessageType]; var handler = m_MessageHandlers[header.MessageType];
using (reader) using (reader)
{ {
@@ -247,17 +269,21 @@ namespace Unity.Netcode
} }
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length); m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
} }
} }
internal unsafe void ProcessIncomingMessageQueue() internal unsafe void ProcessIncomingMessageQueue()
{ {
for (var i = 0; i < m_IncomingMessageQueue.Length; ++i) for (var index = 0; index < m_IncomingMessageQueue.Length; ++index)
{ {
// Avoid copies... // Avoid copies...
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(i); ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(index);
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp); HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp, item.MessageHeaderSerializedSize);
if (m_Disposed)
{
return;
}
} }
m_IncomingMessageQueue.Clear(); m_IncomingMessageQueue.Clear();
@@ -310,12 +336,27 @@ namespace Unity.Netcode
where TMessageType : INetworkMessage where TMessageType : INetworkMessage
where TClientIdListType : IReadOnlyList<ulong> where TClientIdListType : IReadOnlyList<ulong>
{ {
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE; if (clientIds.Count == 0)
var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(MessageHeader), Allocator.Temp, maxSize - sizeof(MessageHeader));
using (tmpSerializer)
{ {
return 0;
}
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
message.Serialize(tmpSerializer); message.Serialize(tmpSerializer);
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
var header = new MessageHeader
{
MessageSize = (ushort)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
};
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageType);
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageSize);
for (var i = 0; i < clientIds.Count; ++i) for (var i = 0; i < clientIds.Count; ++i)
{ {
var clientId = clientIds[i]; var clientId = clientIds[i];
@@ -342,7 +383,7 @@ namespace Unity.Netcode
ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1); ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
if (lastQueueItem.NetworkDelivery != delivery || if (lastQueueItem.NetworkDelivery != delivery ||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
< tmpSerializer.Length + sizeof(MessageHeader)) < tmpSerializer.Length + headerSerializer.Length)
{ {
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob, sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize)); maxSize));
@@ -351,24 +392,18 @@ namespace Unity.Netcode
} }
ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1); ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
writeQueueItem.Writer.TryBeginWrite(sizeof(MessageHeader) + tmpSerializer.Length); writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
var header = new MessageHeader
{
MessageSize = (ushort)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
};
writeQueueItem.Writer.WriteValue(header); writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length); writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
writeQueueItem.BatchHeader.BatchSize++; writeQueueItem.BatchHeader.BatchSize++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + sizeof(MessageHeader)); m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + headerSerializer.Length);
} }
} }
return tmpSerializer.Length; return tmpSerializer.Length + headerSerializer.Length;
}
} }
private struct PointerListWrapper<T> : IReadOnlyList<T> private struct PointerListWrapper<T> : IReadOnlyList<T>
@@ -456,17 +491,17 @@ namespace Unity.Netcode
try try
{ {
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer); m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
}
finally
{
queueItem.Writer.Dispose();
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery); m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
} }
} }
finally
{
queueItem.Writer.Dispose();
}
}
sendQueueItem.Clear(); sendQueueItem.Clear();
} }
} }

View File

@@ -25,5 +25,10 @@ namespace Unity.Netcode
/// The header data that was sent with the message /// The header data that was sent with the message
/// </summary> /// </summary>
public MessageHeader Header; public MessageHeader Header;
/// <summary>
/// The actual serialized size of the header when packed into the buffer
/// </summary>
public int SerializedHeaderSize;
} }
} }

View File

@@ -1,12 +0,0 @@
using System.IO;
namespace Unity.Netcode
{
public static class StreamExtensions
{
public static long SafeGetLengthOrDefault(this Stream stream)
{
return stream.CanSeek ? stream.Length : 0;
}
}
}

View File

@@ -272,18 +272,22 @@ namespace Unity.Netcode
{ {
reader.ReadValueSafe(out int index); reader.ReadValueSafe(out int index);
reader.ReadValueSafe(out T value); reader.ReadValueSafe(out T value);
if (index < m_List.Length) if (index >= m_List.Length)
{ {
m_List[index] = value; throw new Exception("Shouldn't be here, index is higher than list length");
} }
var previousValue = m_List[index];
m_List[index] = value;
if (OnListChanged != null) if (OnListChanged != null)
{ {
OnListChanged(new NetworkListEvent<T> OnListChanged(new NetworkListEvent<T>
{ {
Type = eventType, Type = eventType,
Index = index, Index = index,
Value = value Value = value,
PreviousValue = previousValue
}); });
} }
@@ -293,7 +297,8 @@ namespace Unity.Netcode
{ {
Type = eventType, Type = eventType,
Index = index, Index = index,
Value = value Value = value,
PreviousValue = previousValue
}); });
} }
} }
@@ -368,7 +373,7 @@ namespace Unity.Netcode
public bool Contains(T item) public bool Contains(T item)
{ {
int index = NativeArrayExtensions.IndexOf(m_List, item); int index = NativeArrayExtensions.IndexOf(m_List, item);
return index == -1; return index != -1;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -528,6 +533,11 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public T Value; public T Value;
/// <summary>
/// The previous value when "Value" has changed, if available.
/// </summary>
public T PreviousValue;
/// <summary> /// <summary>
/// the index changed, added or removed if available /// the index changed, added or removed if available
/// </summary> /// </summary>

View File

@@ -9,6 +9,55 @@ namespace Unity.Netcode
[Serializable] [Serializable]
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
{ {
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, ref TForMethod value)
where TForMethod : INetworkSerializable, new()
{
writer.WriteNetworkSerializable(value);
}
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : INetworkSerializable, new()
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize other types
private static void WriteValue<TForMethod>(FastBufferWriter writer, ref TForMethod value) where TForMethod : unmanaged
{
writer.WriteValueSafe(value);
}
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
{
reader.ReadValueSafe(out value);
}
internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, ref TForMethod value);
internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
// These static delegates provide the right implementation for writing and reading a particular network variable
// type.
//
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
//
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
//
// In the future we may be able to use this to provide packing implementations for floats and integers to
// optimize bandwidth usage.
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
internal static WriteDelegate<T> Write = WriteValue;
internal static ReadDelegate<T> Read = ReadValue;
/// <summary> /// <summary>
/// Delegate type for value changed event /// Delegate type for value changed event
/// </summary> /// </summary>
@@ -106,7 +155,7 @@ namespace Unity.Netcode
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{ {
T previousValue = m_InternalValue; T previousValue = m_InternalValue;
reader.ReadValueSafe(out m_InternalValue); Read(reader, out m_InternalValue);
if (keepDirtyDelta) if (keepDirtyDelta)
{ {
@@ -119,13 +168,13 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public override void ReadField(FastBufferReader reader) public override void ReadField(FastBufferReader reader)
{ {
reader.ReadValueSafe(out m_InternalValue); Read(reader, out m_InternalValue);
} }
/// <inheritdoc /> /// <inheritdoc />
public override void WriteField(FastBufferWriter writer) public override void WriteField(FastBufferWriter writer)
{ {
writer.WriteValueSafe(m_InternalValue); Write(writer, ref m_InternalValue);
} }
} }
} }

View File

@@ -0,0 +1,22 @@
namespace Unity.Netcode
{
public class NetworkVariableHelper
{
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
//
// RuntimeAccessModifiersILPP will make this `public`
internal static void InitializeDelegates<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariable<T>.Write = NetworkVariable<T>.WriteNetworkSerializable;
NetworkVariable<T>.Read = NetworkVariable<T>.ReadNetworkSerializable;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 61dd9b1558f6d7c46ad323b2c2c03c29 guid: e54b65208bd3bbe4eaf62ca0384ae21f
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: af81f9951b096ff4cb8e4f8a4106104a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,31 +0,0 @@
using System;
namespace Unity.Netcode
{
internal static class TypeExtensions
{
internal static bool HasInterface(this Type type, Type interfaceType)
{
var ifaces = type.GetInterfaces();
for (int i = 0; i < ifaces.Length; i++)
{
if (ifaces[i] == interfaceType)
{
return true;
}
}
return false;
}
internal static bool IsNullable(this Type type)
{
if (!type.IsValueType)
{
return true; // ref-type
}
return Nullable.GetUnderlyingType(type) != null;
}
}
}

View File

@@ -8,17 +8,22 @@ using UnityEngine.SceneManagement;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// Used for local notifications of various scene events. /// Used for local notifications of various scene events. The <see cref="NetworkSceneManager.OnSceneEvent"/> of
/// The <see cref="NetworkSceneManager.OnSceneEvent"/> of delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide /// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
/// scene event status/state. /// scene event status.<br/>
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <br/>
/// <seealso cref="SceneEventType"/>
/// </summary> /// </summary>
public class SceneEvent public class SceneEvent
{ {
/// <summary> /// <summary>
/// The <see cref="UnityEngine.AsyncOperation"/> returned by <see cref="SceneManager"/> /// The <see cref="UnityEngine.AsyncOperation"/> returned by <see cref="SceneManager"/><BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s: /// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.Load"/> /// <list type="bullet">
/// <see cref="SceneEventType.Unload"/> /// <item><term><see cref="SceneEventType.Load"/></term></item>
/// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// </list>
/// </summary> /// </summary>
public AsyncOperation AsyncOperation; public AsyncOperation AsyncOperation;
@@ -28,71 +33,92 @@ namespace Unity.Netcode
public SceneEventType SceneEventType; public SceneEventType SceneEventType;
/// <summary> /// <summary>
/// If applicable, this reflects the type of scene loading or unloading that is occurring. /// If applicable, this reflects the type of scene loading or unloading that is occurring.<BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s: /// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.Load"/> /// <list type="bullet">
/// <see cref="SceneEventType.Unload"/> /// <item><term><see cref="SceneEventType.Load"/></term></item>
/// <see cref="SceneEventType.LoadComplete"/> /// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// <see cref="SceneEventType.UnloadComplete"/> /// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// <see cref="SceneEventType.LoadEventCompleted"/> /// <item><term><see cref="SceneEventType.UnloadComplete"/></term></item>
/// <see cref="SceneEventType.UnloadEventCompleted"/> /// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary> /// </summary>
public LoadSceneMode LoadSceneMode; public LoadSceneMode LoadSceneMode;
/// <summary> /// <summary>
/// This will be set to the scene name that the event pertains to. /// This will be set to the scene name that the event pertains to.<BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s: /// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.Load"/> /// <list type="bullet">
/// <see cref="SceneEventType.Unload"/> /// <item><term><see cref="SceneEventType.Load"/></term></item>
/// <see cref="SceneEventType.LoadComplete"/> /// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// <see cref="SceneEventType.UnloadComplete"/> /// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// <see cref="SceneEventType.LoadEventCompleted"/> /// <item><term><see cref="SceneEventType.UnloadComplete"/></term></item>
/// <see cref="SceneEventType.UnloadEventCompleted"/> /// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary> /// </summary>
public string SceneName; public string SceneName;
/// <summary> /// <summary>
/// When a scene is loaded, the Scene structure is returned. /// When a scene is loaded, the Scene structure is returned.<BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s: /// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.LoadComplete"/> /// <list type="bullet">
/// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// </list>
/// </summary> /// </summary>
public Scene Scene; public Scene Scene;
/// <summary> /// <summary>
/// Events that always set the <see cref="ClientId"/> to the local client identifier, /// The client identifier can vary depending upon the following conditions: <br/>
/// are initiated (and processed locally) by the server-host, and sent to all clients /// <list type="number">
/// to be processed: /// <item><term><see cref="Netcode.SceneEventType"/>s that always set the <see cref="ClientId"/>
/// <see cref="SceneEventType.Load"/> /// to the local client identifier, are initiated (and processed locally) by the
/// <see cref="SceneEventType.Unload"/> /// server-host, and sent to all clients to be processed.<br/>
/// <see cref="SceneEventType.Synchronize"/> /// <list type="bullet">
/// <see cref="SceneEventType.ReSynchronize"/> /// <item><term><see cref="SceneEventType.Load"/></term></item>
/// /// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// Events that always set the <see cref="ClientId"/> to the local client identifier, /// <item><term><see cref="SceneEventType.Synchronize"/></term></item>
/// <item><term><see cref="SceneEventType.ReSynchronize"/></term></item>
/// </list>
/// </term></item>
/// <item><term>Events that always set the <see cref="ClientId"/> to the local client identifier,
/// are initiated (and processed locally) by a client or server-host, and if initiated /// are initiated (and processed locally) by a client or server-host, and if initiated
/// by a client will always be sent to and processed on the server-host: /// by a client will always be sent to and processed on the server-host:
/// <see cref="SceneEventType.LoadComplete"/> /// <list type="bullet">
/// <see cref="SceneEventType.UnloadComplete"/> /// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// <see cref="SceneEventType.SynchronizeComplete"/> /// <item><term><see cref="SceneEventType.UnloadComplete"/></term></item>
/// /// <item><term><see cref="SceneEventType.SynchronizeComplete"/></term></item>
/// </list>
/// </term></item>
/// <item><term>
/// Events that always set the <see cref="ClientId"/> to the ServerId: /// Events that always set the <see cref="ClientId"/> to the ServerId:
/// <see cref="SceneEventType.LoadEventCompleted"/> /// <list type="bullet">
/// <see cref="SceneEventType.UnloadEventCompleted"/> /// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </term></item>
/// </list>
/// </summary> /// </summary>
public ulong ClientId; public ulong ClientId;
/// <summary> /// <summary>
/// List of clients that completed a loading or unloading event /// List of clients that completed a loading or unloading event.<br/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s: /// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.LoadEventCompleted"/> /// <list type="bullet">
/// <see cref="SceneEventType.UnloadEventCompleted"/> /// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary> /// </summary>
public List<ulong> ClientsThatCompleted; public List<ulong> ClientsThatCompleted;
/// <summary> /// <summary>
/// List of clients that timed out during a loading or unloading event /// List of clients that timed out during a loading or unloading event.<br/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s: /// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.LoadEventCompleted"/> /// <list type="bullet">
/// <see cref="SceneEventType.UnloadEventCompleted"/> /// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary> /// </summary>
public List<ulong> ClientsThatTimedOut; public List<ulong> ClientsThatTimedOut;
} }
@@ -122,34 +148,37 @@ namespace Unity.Netcode
#endif #endif
/// <summary> /// <summary>
/// The delegate callback definition for scene event notifications /// The delegate callback definition for scene event notifications.<br/>
/// For more details review over <see cref="SceneEvent"/> and <see cref="SceneEventData"/> /// See also: <br/>
/// <seealso cref="SceneEvent"/><br/>
/// <seealso cref="SceneEventData"/>
/// </summary> /// </summary>
/// <param name="sceneEvent"></param> /// <param name="sceneEvent"></param>
public delegate void SceneEventDelegate(SceneEvent sceneEvent); public delegate void SceneEventDelegate(SceneEvent sceneEvent);
/// <summary> /// <summary>
/// Event that will notify the local client or server of all scene events that take place /// Subscribe to this event to receive all <see cref="SceneEventType"/> notifications.<br/>
/// For more details review over <see cref="SceneEvent"/>, <see cref="SceneEventData"/>, and <see cref="SceneEventType"/> /// For more details review over <see cref="SceneEvent"/> and <see cref="SceneEventType"/>.<br/>
/// Subscribe to this event to receive all <see cref="SceneEventType"/> notifications /// <b>Alternate Single Event Type Notification Registration Options</b><br/>
///
/// Alternate Single Event Type Notification Registration Options
/// To receive only a specific event type notification or a limited set of notifications you can alternately subscribe to /// To receive only a specific event type notification or a limited set of notifications you can alternately subscribe to
/// each notification type individually via the following events: /// each notification type individually via the following events:<br/>
/// -- <see cref="OnLoad"/> Invoked only when a <see cref="SceneEventType.Load"/> event is being processed /// <list type="bullet">
/// -- <see cref="OnUnload"/> Invoked only when an <see cref="SceneEventType.Unload"/> event is being processed /// <item><term><see cref="OnLoad"/> Invoked only when a <see cref="SceneEventType.Load"/> event is being processed</term></item>
/// -- <see cref="OnSynchronize"/> Invoked only when a <see cref="SceneEventType.Synchronize"/> event is being processed /// <item><term><see cref="OnUnload"/> Invoked only when an <see cref="SceneEventType.Unload"/> event is being processed</term></item>
/// -- <see cref="OnLoadEventCompleted"/> Invoked only when a <see cref="SceneEventType.LoadEventCompleted"/> event is being processed /// <item><term><see cref="OnSynchronize"/> Invoked only when a <see cref="SceneEventType.Synchronize"/> event is being processed</term></item>
/// -- <see cref="OnUnloadEventCompleted"/> Invoked only when an <see cref="SceneEventType.UnloadEventCompleted"/> event is being processed /// <item><term><see cref="OnLoadEventCompleted"/> Invoked only when a <see cref="SceneEventType.LoadEventCompleted"/> event is being processed</term></item>
/// -- <see cref="OnLoadComplete"/> Invoked only when a <see cref="SceneEventType.LoadComplete"/> event is being processed /// <item><term><see cref="OnUnloadEventCompleted"/> Invoked only when an <see cref="SceneEventType.UnloadEventCompleted"/> event is being processed</term></item>
/// -- <see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> event is being processed /// <item><term><see cref="OnLoadComplete"/> Invoked only when a <see cref="SceneEventType.LoadComplete"/> event is being processed</term></item>
/// -- <see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed /// <item><term><see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> event is being processed</term></item>
/// <item><term><see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed</term></item>
/// </list>
/// </summary> /// </summary>
public event SceneEventDelegate OnSceneEvent; public event SceneEventDelegate OnSceneEvent;
/// <summary> /// <summary>
/// Delegate declaration for the OnLoad event /// Delegate declaration for the OnLoad event.<br/>
/// View <see cref="SceneEventType.Load"/> for more information /// See also: <br/>
/// <seealso cref="SceneEventType.Load"/>for more information
/// </summary> /// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param> /// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">name of the scene being processed</param> /// <param name="sceneName">name of the scene being processed</param>
@@ -158,8 +187,9 @@ namespace Unity.Netcode
public delegate void OnLoadDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation); public delegate void OnLoadDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation);
/// <summary> /// <summary>
/// Delegate declaration for the OnUnload event /// Delegate declaration for the OnUnload event.<br/>
/// View <see cref="SceneEventType.Unload"/> for more information /// See also: <br/>
/// <seealso cref="SceneEventType.Unload"/> for more information
/// </summary> /// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param> /// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">name of the scene being processed</param> /// <param name="sceneName">name of the scene being processed</param>
@@ -167,16 +197,18 @@ namespace Unity.Netcode
public delegate void OnUnloadDelegateHandler(ulong clientId, string sceneName, AsyncOperation asyncOperation); public delegate void OnUnloadDelegateHandler(ulong clientId, string sceneName, AsyncOperation asyncOperation);
/// <summary> /// <summary>
/// Delegate declaration for the OnSynchronize event /// Delegate declaration for the OnSynchronize event.<br/>
/// View <see cref="SceneEventType.Synchronize"/> for more information /// See also: <br/>
/// <seealso cref="SceneEventType.Synchronize"/> for more information
/// </summary> /// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param> /// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
public delegate void OnSynchronizeDelegateHandler(ulong clientId); public delegate void OnSynchronizeDelegateHandler(ulong clientId);
/// <summary> /// <summary>
/// Delegate declaration for the OnLoadEventCompleted and OnUnloadEventCompleted events /// Delegate declaration for the OnLoadEventCompleted and OnUnloadEventCompleted events.<br/>
/// View <see cref="SceneEventType.LoadEventCompleted"/> for more information /// See also:<br/>
/// View <see cref="SceneEventType.UnloadEventCompleted"/> for more information /// <seealso cref="SceneEventType.LoadEventCompleted"/><br/>
/// <seealso cref="SceneEventType.UnloadEventCompleted"/>
/// </summary> /// </summary>
/// <param name="sceneName">scene pertaining to this event</param> /// <param name="sceneName">scene pertaining to this event</param>
/// <param name="loadSceneMode"><see cref="LoadSceneMode"/> of the associated event completed</param> /// <param name="loadSceneMode"><see cref="LoadSceneMode"/> of the associated event completed</param>
@@ -185,8 +217,9 @@ namespace Unity.Netcode
public delegate void OnEventCompletedDelegateHandler(string sceneName, LoadSceneMode loadSceneMode, List<ulong> clientsCompleted, List<ulong> clientsTimedOut); public delegate void OnEventCompletedDelegateHandler(string sceneName, LoadSceneMode loadSceneMode, List<ulong> clientsCompleted, List<ulong> clientsTimedOut);
/// <summary> /// <summary>
/// Delegate declaration for the OnLoadComplete event /// Delegate declaration for the OnLoadComplete event.<br/>
/// View <see cref="SceneEventType.LoadComplete"/> for more information /// See also:<br/>
/// <seealso cref="SceneEventType.LoadComplete"/> for more information
/// </summary> /// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param> /// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">the scene name pertaining to this event</param> /// <param name="sceneName">the scene name pertaining to this event</param>
@@ -194,38 +227,40 @@ namespace Unity.Netcode
public delegate void OnLoadCompleteDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode); public delegate void OnLoadCompleteDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode);
/// <summary> /// <summary>
/// Delegate declaration for the OnUnloadComplete event /// Delegate declaration for the OnUnloadComplete event.<br/>
/// View <see cref="SceneEventType.UnloadComplete"/> for more information /// See also:<br/>
/// <seealso cref="SceneEventType.UnloadComplete"/> for more information
/// </summary> /// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param> /// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">the scene name pertaining to this event</param> /// <param name="sceneName">the scene name pertaining to this event</param>
public delegate void OnUnloadCompleteDelegateHandler(ulong clientId, string sceneName); public delegate void OnUnloadCompleteDelegateHandler(ulong clientId, string sceneName);
/// <summary> /// <summary>
/// Delegate declaration for the OnSynchronizeComplete event /// Delegate declaration for the OnSynchronizeComplete event.<br/>
/// View <see cref="SceneEventType.SynchronizeComplete"/> for more information /// See also:<br/>
/// <seealso cref="SceneEventType.SynchronizeComplete"/> for more information
/// </summary> /// </summary>
/// <param name="clientId">the client that completed this event</param> /// <param name="clientId">the client that completed this event</param>
public delegate void OnSynchronizeCompleteDelegateHandler(ulong clientId); public delegate void OnSynchronizeCompleteDelegateHandler(ulong clientId);
/// <summary> /// <summary>
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server /// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server.<br/>
/// The server and client(s) will receive this notification /// <em>Note: The server and connected client(s) will always receive this notification.</em>
/// </summary> /// </summary>
public event OnLoadDelegateHandler OnLoad; public event OnLoadDelegateHandler OnLoad;
/// <summary> /// <summary>
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server /// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server.<br/>
/// The server and client(s) will receive this notification /// <em>Note: The server and connected client(s) will always receive this notification.</em>
/// </summary> /// </summary>
public event OnUnloadDelegateHandler OnUnload; public event OnUnloadDelegateHandler OnUnload;
/// <summary> /// <summary>
/// Invoked when a <see cref="SceneEventType.Synchronize"/> event is started by the server /// Invoked when a <see cref="SceneEventType.Synchronize"/> event is started by the server
/// after a client is approved for connection in order to synchronize the client with the currently loaded /// after a client is approved for connection in order to synchronize the client with the currently loaded
/// scenes and NetworkObjects. This event signifies the beginning of the synchronization event. /// scenes and NetworkObjects. This event signifies the beginning of the synchronization event.<br/>
/// The server and client will receive this notification /// <em>Note: The server and connected client(s) will always receive this notification.
/// Note: this event is generated on a per newly connected and approved client basis /// This event is generated on a per newly connected and approved client basis.</em>
/// </summary> /// </summary>
public event OnSynchronizeDelegateHandler OnSynchronize; public event OnSynchronizeDelegateHandler OnSynchronize;
@@ -233,8 +268,8 @@ namespace Unity.Netcode
/// Invoked when a <see cref="SceneEventType.LoadEventCompleted"/> event is generated by the server. /// Invoked when a <see cref="SceneEventType.LoadEventCompleted"/> event is generated by the server.
/// This event signifies the end of an existing <see cref="SceneEventType.Load"/> event as it pertains /// This event signifies the end of an existing <see cref="SceneEventType.Load"/> event as it pertains
/// to all clients connected when the event was started. This event signifies that all clients (and server) have /// to all clients connected when the event was started. This event signifies that all clients (and server) have
/// finished the <see cref="SceneEventType.Load"/> event. /// finished the <see cref="SceneEventType.Load"/> event.<br/>
/// Note: this is useful to know when all clients have loaded the same scene (single or additive mode) /// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em>
/// </summary> /// </summary>
public event OnEventCompletedDelegateHandler OnLoadEventCompleted; public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
@@ -242,32 +277,31 @@ namespace Unity.Netcode
/// Invoked when a <see cref="SceneEventType.UnloadEventCompleted"/> event is generated by the server. /// Invoked when a <see cref="SceneEventType.UnloadEventCompleted"/> event is generated by the server.
/// This event signifies the end of an existing <see cref="SceneEventType.Unload"/> event as it pertains /// This event signifies the end of an existing <see cref="SceneEventType.Unload"/> event as it pertains
/// to all clients connected when the event was started. This event signifies that all clients (and server) have /// to all clients connected when the event was started. This event signifies that all clients (and server) have
/// finished the <see cref="SceneEventType.Unload"/> event. /// finished the <see cref="SceneEventType.Unload"/> event.<br/>
/// Note: this is useful to know when all clients have unloaded a specific scene. The <see cref="LoadSceneMode"/> will /// <em>Note: this is useful to know when all clients have unloaded a specific scene. The <see cref="LoadSceneMode"/> will
/// always be <see cref="LoadSceneMode.Additive"/> for this event /// always be <see cref="LoadSceneMode.Additive"/> for this event.</em>
/// </summary> /// </summary>
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted; public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
/// <summary> /// <summary>
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server. /// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.<br/>
/// The server receives this message from all clients (including itself). /// <em>Note: The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server. /// Each client receives their own notification sent to the server.</em>
/// </summary> /// </summary>
public event OnLoadCompleteDelegateHandler OnLoadComplete; public event OnLoadCompleteDelegateHandler OnLoadComplete;
/// <summary> /// <summary>
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server. /// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.<br/>
/// The server receives this message from all clients (including itself). /// <em>Note: The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server. /// Each client receives their own notification sent to the server.</em>
/// </summary> /// </summary>
public event OnUnloadCompleteDelegateHandler OnUnloadComplete; public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
/// <summary> /// <summary>
/// Invoked when a <see cref="SceneEventType.SynchronizeComplete"/> event is generated by a client. /// Invoked when a <see cref="SceneEventType.SynchronizeComplete"/> event is generated by a client. <br/>
/// The server receives this message from the client, but will never generate this event for itself. /// <em> Note: The server receives this message from the client, but will never generate this event for itself.
/// Each client receives their own notification sent to the server. /// Each client receives their own notification sent to the server. This is useful to know that a client has
/// Note: This is useful to know that a client has completed the entire connection sequence, loaded all scenes, and /// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects.</em>
/// synchronized all NetworkObjects.
/// </summary> /// </summary>
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete; public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
@@ -284,9 +318,9 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Delegate handler defined by <see cref="VerifySceneBeforeLoadingDelegateHandler"/> that is invoked before the /// Delegate handler defined by <see cref="VerifySceneBeforeLoadingDelegateHandler"/> that is invoked before the
/// server or client loads a scene during an active netcode game session. /// server or client loads a scene during an active netcode game session.<br/>
/// Client Side: In order for clients to be notified of this condition you must assign the <see cref="VerifySceneBeforeLoading"/> delegate handler. /// <b>Client Side:</b> In order for clients to be notified of this condition you must assign the <see cref="VerifySceneBeforeLoading"/> delegate handler.<br/>
/// Server Side: <see cref="LoadScene(string, LoadSceneMode)"/> will return <see cref="SceneEventProgressStatus.SceneFailedVerification"/>. /// <b>Server Side:</b> <see cref="LoadScene(string, LoadSceneMode)"/> will return <see cref="SceneEventProgressStatus"/>.
/// </summary> /// </summary>
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading; public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
@@ -353,11 +387,10 @@ namespace Unity.Netcode
internal Scene DontDestroyOnLoadScene; internal Scene DontDestroyOnLoadScene;
/// <summary> /// <summary>
/// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and /// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and
/// the server's currently active scene will be loaded in single mode on the client /// the server's currently active scene will be loaded in single mode on the client
/// unless it was already loaded. /// unless it was already loaded.<br/>
/// /// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
/// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the /// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
/// <see cref="VerifySceneBeforeLoading"/> method. /// <see cref="VerifySceneBeforeLoading"/> method.
/// </summary> /// </summary>
@@ -506,12 +539,11 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// This will change how clients are initially synchronized. /// This will change how clients are initially synchronized.<br/>
/// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and /// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and
/// the server's currently active scene will be loaded in single mode on the client /// the server's currently active scene will be loaded in single mode on the client
/// unless it was already loaded. /// unless it was already loaded. <br/>
/// /// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
/// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the /// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
/// <see cref="VerifySceneBeforeLoading"/> method. /// <see cref="VerifySceneBeforeLoading"/> method.
/// </summary> /// </summary>
@@ -533,20 +565,8 @@ namespace Unity.Netcode
GenerateScenesInBuild(); GenerateScenesInBuild();
// If NetworkManager has this set to true, then we can get the DDOL (DontDestroyOnLoad) from its GaemObject // Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
if (networkManager.DontDestroy)
{
DontDestroyOnLoadScene = networkManager.gameObject.scene; DontDestroyOnLoadScene = networkManager.gameObject.scene;
}
else
{
// Otherwise, we have to create a GameObject and move it into the DDOL in order to
// register the DDOL scene handle with NetworkSceneManager
var myDDOLObject = new GameObject("DDOL-NWSM");
UnityEngine.Object.DontDestroyOnLoad(myDDOLObject);
DontDestroyOnLoadScene = myDDOLObject.scene;
UnityEngine.Object.Destroy(myDDOLObject);
}
ServerSceneHandleToClientSceneHandle.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle); ServerSceneHandleToClientSceneHandle.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle);
ScenesLoaded.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene); ScenesLoaded.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
@@ -858,7 +878,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Server Side: /// <b>Server Side:</b>
/// Unloads an additively loaded scene. If you want to unload a <see cref="LoadSceneMode.Single"/> mode loaded scene load another <see cref="LoadSceneMode.Single"/> scene. /// Unloads an additively loaded scene. If you want to unload a <see cref="LoadSceneMode.Single"/> mode loaded scene load another <see cref="LoadSceneMode.Single"/> scene.
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via the <see cref="OnSceneEvent"/> /// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via the <see cref="OnSceneEvent"/>
/// </summary> /// </summary>
@@ -918,7 +938,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Client Side: /// <b>Client Side:</b>
/// Handles <see cref="SceneEventType.Unload"/> scene events. /// Handles <see cref="SceneEventType.Unload"/> scene events.
/// </summary> /// </summary>
private void OnClientUnloadScene(uint sceneEventId) private void OnClientUnloadScene(uint sceneEventId)
@@ -996,8 +1016,8 @@ namespace Unity.Netcode
// despawned that no longer exists // despawned that no longer exists
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray()); SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray());
//Second, server sets itself as having finished unloading //Only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId)) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
} }
@@ -1056,7 +1076,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Server side: /// <b>Server side:</b>
/// Loads the scene name in either additive or single loading mode. /// Loads the scene name in either additive or single loading mode.
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via <see cref="OnSceneEvent"/> /// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via <see cref="OnSceneEvent"/>
/// </summary> /// </summary>
@@ -1312,8 +1332,8 @@ namespace Unity.Netcode
OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
//Second, set the server as having loaded for the associated SceneEventProgress //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId)) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
} }
@@ -1798,21 +1818,18 @@ namespace Unity.Netcode
/// Moves all NetworkObjects that don't have the <see cref="NetworkObject.DestroyWithScene"/> set to /// Moves all NetworkObjects that don't have the <see cref="NetworkObject.DestroyWithScene"/> set to
/// the "Do not destroy on load" scene. /// the "Do not destroy on load" scene.
/// </summary> /// </summary>
private void MoveObjectsToDontDestroyOnLoad() internal void MoveObjectsToDontDestroyOnLoad()
{ {
// Move ALL NetworkObjects to the temp scene // Move ALL NetworkObjects marked to persist scene transitions into the DDOL scene
var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList); var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);
foreach (var sobj in objectsToKeep) foreach (var sobj in objectsToKeep)
{ {
if (!sobj.DestroyWithScene || (sobj.IsSceneObject != null && sobj.IsSceneObject.Value && sobj.gameObject.scene == DontDestroyOnLoadScene)) if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene)
{ {
// Only move objects with no parent as child objects will follow // Only move dynamically spawned network objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null) if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
{ {
UnityEngine.Object.DontDestroyOnLoad(sobj.gameObject); UnityEngine.Object.DontDestroyOnLoad(sobj.gameObject);
// Since we are doing a scene transition, disable the GameObject until the next scene is loaded
sobj.gameObject.SetActive(false);
} }
} }
else if (m_NetworkManager.IsServer) else if (m_NetworkManager.IsServer)
@@ -1875,26 +1892,24 @@ namespace Unity.Netcode
/// Moves all spawned NetworkObjects (from do not destroy on load) to the scene specified /// Moves all spawned NetworkObjects (from do not destroy on load) to the scene specified
/// </summary> /// </summary>
/// <param name="scene">scene to move the NetworkObjects to</param> /// <param name="scene">scene to move the NetworkObjects to</param>
private void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
{ {
// Move ALL NetworkObjects to the temp scene // Move ALL NetworkObjects to the temp scene
var objectsToKeep = m_NetworkManager.SpawnManager.SpawnedObjectsList; var objectsToKeep = m_NetworkManager.SpawnManager.SpawnedObjectsList;
foreach (var sobj in objectsToKeep) foreach (var sobj in objectsToKeep)
{ {
if (sobj.gameObject.scene == DontDestroyOnLoadScene && (sobj.IsSceneObject == null || sobj.IsSceneObject.Value)) // If it is in the DDOL then
if (sobj.gameObject.scene == DontDestroyOnLoadScene)
{ {
continue; // only move dynamically spawned network objects, with no parent as child objects will follow,
} // back into the currently active scene
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
// Only move objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null)
{ {
// set it back to active at this point
sobj.gameObject.SetActive(true);
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene); SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
} }
} }
} }
} }
} }
}

View File

@@ -8,84 +8,86 @@ using UnityEngine.SceneManagement;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// The different types of scene events communicated between a server and client. /// The different types of scene events communicated between a server and client. <br/>
/// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages /// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages.<br/>
/// Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled /// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <see cref="SceneEvent"/> /// See also: <br/>
/// <seealso cref="SceneEvent"/>
/// </summary> /// </summary>
public enum SceneEventType : byte public enum SceneEventType : byte
{ {
/// <summary> /// <summary>
/// Load a scene /// Load a scene<br/>
/// Invocation: Server Side /// <b>Invocation:</b> Server Side<br/>
/// Message Flow: Server to client /// <b>Message Flow:</b> Server to client<br/>
/// Event Notification: Both server and client are notified a load scene event started /// <b>Event Notification:</b> Both server and client are notified a load scene event started
/// </summary> /// </summary>
Load, Load,
/// <summary> /// <summary>
/// Unload a scene /// Unload a scene<br/>
/// Invocation: Server Side /// <b>Invocation:</b> Server Side<br/>
/// Message Flow: Server to client /// <b>Message Flow:</b> Server to client<br/>
/// Event Notification: Both server and client are notified an unload scene event started /// <b>Event Notification:</b> Both server and client are notified an unload scene event started.
/// </summary> /// </summary>
Unload, Unload,
/// <summary> /// <summary>
/// Synchronize current game session state for approved clients /// Synchronizes current game session state for newly approved clients<br/>
/// Invocation: Server Side /// <b>Invocation:</b> Server Side<br/>
/// Message Flow: Server to client /// <b>Message Flow:</b> Server to client<br/>
/// Event Notification: Server and Client receives a local notification (server receives the ClientId being synchronized) /// <b>Event Notification:</b> Server and Client receives a local notification (<em>server receives the ClientId being synchronized</em>).
/// </summary> /// </summary>
Synchronize, Synchronize,
/// <summary> /// <summary>
/// Game session re-synchronization of NetworkObjects that were destroyed during a <see cref="Synchronize"/> event /// Game session re-synchronization of NetworkObjects that were destroyed during a <see cref="Synchronize"/> event<br/>
/// Invocation: Server Side /// <b>Invocation:</b> Server Side<br/>
/// Message Flow: Server to client /// <b>Message Flow:</b> Server to client<br/>
/// Event Notification: Both server and client receive a local notification /// <b>Event Notification:</b> Both server and client receive a local notification<br/>
/// Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point /// <em>Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point.</em>
/// </summary> /// </summary>
ReSynchronize, ReSynchronize,
/// <summary> /// <summary>
/// All clients have finished loading a scene /// All clients have finished loading a scene<br/>
/// Invocation: Server Side /// <b>Invocation:</b> Server Side<br/>
/// Message Flow: Server to Client /// <b>Message Flow:</b> Server to Client<br/>
/// Event Notification: Both server and client receive a local notification containing the clients that finished /// <b>Event Notification:</b> Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out (if any). /// as well as the clients that timed out(<em>if any</em>).
/// </summary> /// </summary>
LoadEventCompleted, LoadEventCompleted,
/// <summary> /// <summary>
/// All clients have unloaded a scene /// All clients have unloaded a scene<br/>
/// Invocation: Server Side /// <b>Invocation:</b> Server Side<br/>
/// Message Flow: Server to Client /// <b>Message Flow:</b> Server to Client<br/>
/// Event Notification: Both server and client receive a local notification containing the clients that finished /// <b>Event Notification:</b> Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out (if any). /// as well as the clients that timed out(<em>if any</em>).
/// </summary> /// </summary>
UnloadEventCompleted, UnloadEventCompleted,
/// <summary> /// <summary>
/// A client has finished loading a scene /// A client has finished loading a scene<br/>
/// Invocation: Client Side /// <b>Invocation:</b> Client Side<br/>
/// Message Flow: Client to Server /// <b>Message Flow:</b> Client to Server<br/>
/// Event Notification: Both server and client receive a local notification /// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary> /// </summary>
LoadComplete, LoadComplete,
/// <summary> /// <summary>
/// A client has finished unloading a scene /// A client has finished unloading a scene<br/>
/// Invocation: Client Side /// <b>Invocation:</b> Client Side<br/>
/// Message Flow: Client to Server /// <b>Message Flow:</b> Client to Server<br/>
/// Event Notification: Both server and client receive a local notification /// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary> /// </summary>
UnloadComplete, UnloadComplete,
/// <summary> /// <summary>
/// A client has finished synchronizing from a <see cref="Synchronize"/> event /// A client has finished synchronizing from a <see cref="Synchronize"/> event<br/>
/// Invocation: Client Side /// <b>Invocation:</b> Client Side<br/>
/// Message Flow: Client to Server /// <b>Message Flow:</b> Client to Server<br/>
/// Event Notification: Both server and client receive a local notification /// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary> /// </summary>
SynchronizeComplete, SynchronizeComplete,
} }
/// <summary> /// <summary>
/// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages /// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages
/// Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled /// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <seealso cref="SceneEvent"/>
/// </summary> /// </summary>
internal class SceneEventData : IDisposable internal class SceneEventData : IDisposable
{ {

View File

@@ -9,10 +9,10 @@ namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// Used by <see cref="NetworkSceneManager"/> to determine if a server invoked scene event has started. /// Used by <see cref="NetworkSceneManager"/> to determine if a server invoked scene event has started.
/// The returned status is stored in the <see cref="SceneEventProgress.Status"/> property. /// The returned status is stored in the <see cref="SceneEventProgress.Status"/> property.<br/>
/// Note: This was formally known as SwitchSceneProgress which contained the <see cref="AsyncOperation"/>. /// <em>Note: This was formally known as SwitchSceneProgress which contained the <see cref="AsyncOperation"/>.
/// All <see cref="AsyncOperation"/>s are now delivered by the <see cref="NetworkSceneManager.OnSceneEvent"/> event handler /// All <see cref="AsyncOperation"/>s are now delivered by the <see cref="NetworkSceneManager.OnSceneEvent"/> event handler
/// via the <see cref="SceneEvent"/> parameter. /// via the <see cref="SceneEvent"/> parameter.</em>
/// </summary> /// </summary>
public enum SceneEventProgressStatus public enum SceneEventProgressStatus
{ {
@@ -21,31 +21,30 @@ namespace Unity.Netcode
/// </summary> /// </summary>
None, None,
/// <summary> /// <summary>
/// The scene event was successfully started /// The scene event was successfully started.
/// </summary> /// </summary>
Started, Started,
/// <summary> /// <summary>
/// Returned if you try to unload a scene that was not yet loaded /// Returned if you try to unload a scene that was not yet loaded.
/// </summary> /// </summary>
SceneNotLoaded, SceneNotLoaded,
/// <summary> /// <summary>
/// Returned if you try to start a new scene event before a previous one is finished /// Returned if you try to start a new scene event before a previous one is finished.
/// </summary> /// </summary>
SceneEventInProgress, SceneEventInProgress,
/// <summary> /// <summary>
/// Returned if the scene name used with <see cref="NetworkSceneManager.LoadScene(string, LoadSceneMode)"/> /// Returned if the scene name used with <see cref="NetworkSceneManager.LoadScene(string, LoadSceneMode)"/>
/// or <see cref="NetworkSceneManager.UnloadScene(Scene)"/>is invalid /// or <see cref="NetworkSceneManager.UnloadScene(Scene)"/>is invalid.
/// </summary> /// </summary>
InvalidSceneName, InvalidSceneName,
/// <summary> /// <summary>
/// Server side: Returned if the <see cref="NetworkSceneManager.VerifySceneBeforeLoading"/> delegate handler returns false /// Server side: Returned if the <see cref="NetworkSceneManager.VerifySceneBeforeLoading"/> delegate handler returns false
/// (i.e. scene is considered not valid/safe to load) /// (<em>i.e. scene is considered not valid/safe to load</em>).
/// </summary> /// </summary>
SceneFailedVerification, SceneFailedVerification,
/// <summary> /// <summary>
/// This is used for internal error notifications. /// This is used for internal error notifications.<br/>
/// If you receive this event then it is most likely due to a bug. /// If you receive this event then it is most likely due to a bug (<em>please open a GitHub issue with steps to replicate</em>).<br/>
/// If you receive this event repeatedly, then please open a GitHub issue with steps to replicate
/// </summary> /// </summary>
InternalNetcodeError, InternalNetcodeError,
} }

View File

@@ -24,6 +24,8 @@ namespace Unity.Netcode
internal readonly unsafe WriterHandle* Handle; internal readonly unsafe WriterHandle* Handle;
private static byte[] s_ByteArrayCache = new byte[65535];
/// <summary> /// <summary>
/// The current write position /// The current write position
/// </summary> /// </summary>
@@ -78,6 +80,10 @@ namespace Unity.Netcode
/// <param name="maxSize">Maximum size the buffer can grow to. If less than size, buffer cannot grow.</param> /// <param name="maxSize">Maximum size the buffer can grow to. If less than size, buffer cannot grow.</param>
public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1) public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1)
{ {
// Allocating both the Handle struct and the buffer in a single allocation - sizeof(WriterHandle) + size
// The buffer for the initial allocation is the next block of memory after the handle itself.
// If the buffer grows, a new buffer will be allocated and the handle pointer pointed at the new location...
// The original buffer won't be deallocated until the writer is destroyed since it's part of the handle allocation.
Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf<WriterHandle>(), allocator); Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf<WriterHandle>(), allocator);
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size); UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size);
@@ -183,7 +189,7 @@ namespace Unity.Netcode
var newSize = Math.Min(desiredSize, Handle->MaxCapacity); var newSize = Math.Min(desiredSize, Handle->MaxCapacity);
byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf<byte>(), Handle->Allocator); byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf<byte>(), Handle->Allocator);
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
UnsafeUtility.MemSet(newBuffer, 0, sizeof(WriterHandle) + newSize); UnsafeUtility.MemSet(newBuffer, 0, newSize);
#endif #endif
UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length); UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length);
if (Handle->BufferGrew) if (Handle->BufferGrew)
@@ -349,6 +355,29 @@ namespace Unity.Netcode
return ret; return ret;
} }
/// <summary>
/// Uses a static cached array to create an array segment with no allocations.
/// This array can only be used until the next time ToTempByteArray() is called on ANY FastBufferWriter,
/// as the cached buffer is shared by all of them and will be overwritten.
/// As such, this should be used with care.
/// </summary>
/// <returns></returns>
internal unsafe ArraySegment<byte> ToTempByteArray()
{
var length = Length;
if (length > s_ByteArrayCache.Length)
{
return new ArraySegment<byte>(ToArray(), 0, length);
}
fixed (byte* b = s_ByteArrayCache)
{
UnsafeUtility.MemCpy(b, Handle->BufferPointer, length);
}
return new ArraySegment<byte>(s_ByteArrayCache, 0, length);
}
/// <summary> /// <summary>
/// Gets a direct pointer to the underlying buffer /// Gets a direct pointer to the underlying buffer
/// </summary> /// </summary>
@@ -399,7 +428,7 @@ namespace Unity.Netcode
/// <param name="count"></param> /// <param name="count"></param>
/// <param name="offset"></param> /// <param name="offset"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public void WriteNetworkSerializable<T>(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable public void WriteNetworkSerializable<T>(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable
{ {
int sizeInTs = count != -1 ? count : array.Length - offset; int sizeInTs = count != -1 ? count : array.Length - offset;
WriteValueSafe(sizeInTs); WriteValueSafe(sizeInTs);

View File

@@ -27,6 +27,7 @@ namespace Unity.Netcode
public MessageHeader Header; public MessageHeader Header;
public ulong SenderId; public ulong SenderId;
public float Timestamp; public float Timestamp;
public int SerializedHeaderSize;
} }
private struct TriggerInfo private struct TriggerInfo
{ {
@@ -117,7 +118,8 @@ namespace Unity.Netcode
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length), Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
Header = context.Header, Header = context.Header,
Timestamp = context.Timestamp, Timestamp = context.Timestamp,
SenderId = context.SenderId SenderId = context.SenderId,
SerializedHeaderSize = context.SerializedHeaderSize
}); });
} }
@@ -154,6 +156,24 @@ namespace Unity.Netcode
m_Triggers.Remove(staleKeys[i]); m_Triggers.Remove(staleKeys[i]);
} }
} }
/// <summary>
/// Cleans up any trigger that's existed for more than a second.
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
/// </summary>
internal void CleanupAllTriggers()
{
foreach (var kvp in m_Triggers)
{
foreach (var data in kvp.Value.TriggerData)
{
data.Reader.Dispose();
}
kvp.Value.TriggerData.Dispose();
}
m_Triggers.Clear();
}
internal void RemoveOwnership(NetworkObject networkObject) internal void RemoveOwnership(NetworkObject networkObject)
{ {
@@ -167,13 +187,21 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is not spawned"); throw new SpawnStateException("Object is not spawned");
} }
for (int i = NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.Count - 1; // If we made it here then we are the server and if the server is determined to already be the owner
i > -1; // then ignore the RemoveOwnership invocation.
i--) if (networkObject.OwnerClientId == NetworkManager.ServerClientId)
{ {
if (NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects[i] == networkObject) return;
}
// Make sure the connected client entry exists before trying to remove ownership.
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{ {
NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.RemoveAt(i); for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
{
if (networkClient.OwnedObjects[i] == networkObject)
{
networkClient.OwnedObjects.RemoveAt(i);
} }
} }
@@ -191,6 +219,14 @@ namespace Unity.Netcode
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
} }
} }
else
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
}
}
}
/// <summary> /// <summary>
/// Helper function to get a network client for a clientId from the NetworkManager. /// Helper function to get a network client for a clientId from the NetworkManager.
@@ -207,7 +243,7 @@ namespace Unity.Netcode
return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient); return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient);
} }
if (clientId == NetworkManager.LocalClient.ClientId) if (NetworkManager.LocalClient != null && clientId == NetworkManager.LocalClient.ClientId)
{ {
networkClient = NetworkManager.LocalClient; networkClient = NetworkManager.LocalClient;
return true; return true;
@@ -483,7 +519,7 @@ namespace Unity.Netcode
foreach (var trigger in triggerInfo.TriggerData) foreach (var trigger in triggerInfo.TriggerData)
{ {
// Reader will be disposed within HandleMessage // Reader will be disposed within HandleMessage
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp); NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp, trigger.SerializedHeaderSize);
} }
triggerInfo.TriggerData.Dispose(); triggerInfo.TriggerData.Dispose();
@@ -580,20 +616,29 @@ namespace Unity.Netcode
} }
} }
internal void DestroyNonSceneObjects() internal void DespawnAndDestroyNetworkObjects()
{ {
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(); var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
for (int i = 0; i < networkObjects.Length; i++) for (int i = 0; i < networkObjects.Length; i++)
{ {
if (networkObjects[i].NetworkManager == NetworkManager) if (networkObjects[i].NetworkManager == NetworkManager)
{
if (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value == false)
{ {
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i])) if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
{ {
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
OnDespawnObject(networkObjects[i], false); OnDespawnObject(networkObjects[i], false);
// Leave destruction up to the handler
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
}
else if (networkObjects[i].IsSpawned)
{
// If it is an in-scene placed NetworkObject then just despawn
// and let it be destroyed when the scene is unloaded. Otherwise,
// despawn and destroy it.
var shouldDestroy = !(networkObjects[i].IsSceneObject != null
&& networkObjects[i].IsSceneObject.Value);
OnDespawnObject(networkObjects[i], shouldDestroy);
} }
else else
{ {
@@ -602,7 +647,6 @@ namespace Unity.Netcode
} }
} }
} }
}
internal void DestroySceneObjects() internal void DestroySceneObjects()
{ {
@@ -668,6 +712,9 @@ namespace Unity.Netcode
return; return;
} }
// If we are shutting down the NetworkManager, then ignore resetting the parent
if (!NetworkManager.ShutdownInProgress)
{
// Move child NetworkObjects to the root when parent NetworkObject is destroyed // Move child NetworkObjects to the root when parent NetworkObject is destroyed
foreach (var spawnedNetObj in SpawnedObjectsList) foreach (var spawnedNetObj in SpawnedObjectsList)
{ {
@@ -682,6 +729,7 @@ namespace Unity.Netcode
} }
} }
} }
}
if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient)) if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{ {

View File

@@ -114,6 +114,11 @@ namespace Unity.Netcode
{ {
double d = m_TimeSec / m_TickInterval; double d = m_TimeSec / m_TickInterval;
m_CachedTick = (int)d; m_CachedTick = (int)d;
// This check is needed due to double division imprecision of large numbers
if ((d - m_CachedTick) >= 0.999999999999)
{
m_CachedTick++;
}
m_CachedTickOffset = ((d - Math.Truncate(d)) * m_TickInterval); m_CachedTickOffset = ((d - Math.Truncate(d)) * m_TickInterval);
// This handles negative time, decreases tick by 1 and makes offset positive. // This handles negative time, decreases tick by 1 and makes offset positive.

View File

@@ -26,6 +26,7 @@ namespace Unity.Netcode.Samples
protected override void Update() protected override void Update()
{ {
CanCommitToTransform = IsOwner;
base.Update(); base.Update();
if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening)) if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
{ {

View File

@@ -92,7 +92,7 @@ namespace Unity.Netcode.EditorTests
var reader = new FastBufferReader(writer, Allocator.Temp); var reader = new FastBufferReader(writer, Allocator.Temp);
using (reader) using (reader)
{ {
m_MessagingSystem.HandleMessage(messageHeader, reader, 0, 0); m_MessagingSystem.HandleMessage(messageHeader, reader, 0, 0, 0);
Assert.IsTrue(TestMessage.Deserialized); Assert.IsTrue(TestMessage.Deserialized);
Assert.AreEqual(1, TestMessage.DeserializedValues.Count); Assert.AreEqual(1, TestMessage.DeserializedValues.Count);
Assert.AreEqual(message, TestMessage.DeserializedValues[0]); Assert.AreEqual(message, TestMessage.DeserializedValues[0]);
@@ -143,7 +143,7 @@ namespace Unity.Netcode.EditorTests
}; };
var messageHeader = new MessageHeader var messageHeader = new MessageHeader
{ {
MessageSize = (ushort)UnsafeUtility.SizeOf<TestMessage>(), MessageSize = (uint)UnsafeUtility.SizeOf<TestMessage>(),
MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)), MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)),
}; };
var message = GetMessage(); var message = GetMessage();
@@ -151,12 +151,10 @@ namespace Unity.Netcode.EditorTests
var writer = new FastBufferWriter(1300, Allocator.Temp); var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer) using (writer)
{ {
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) + writer.WriteValueSafe(batchHeader);
FastBufferWriter.GetWriteSize(messageHeader) + BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType);
FastBufferWriter.GetWriteSize(message)); BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize);
writer.WriteValue(batchHeader); writer.WriteValueSafe(message);
writer.WriteValue(messageHeader);
writer.WriteValue(message);
var reader = new FastBufferReader(writer, Allocator.Temp); var reader = new FastBufferReader(writer, Allocator.Temp);
using (reader) using (reader)
@@ -188,14 +186,13 @@ namespace Unity.Netcode.EditorTests
var writer = new FastBufferWriter(1300, Allocator.Temp); var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer) using (writer)
{ {
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) + writer.WriteValueSafe(batchHeader);
FastBufferWriter.GetWriteSize(messageHeader) * 2 + BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType);
FastBufferWriter.GetWriteSize(message) * 2); BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize);
writer.WriteValue(batchHeader); writer.WriteValueSafe(message);
writer.WriteValue(messageHeader); BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType);
writer.WriteValue(message); BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize);
writer.WriteValue(messageHeader); writer.WriteValueSafe(message2);
writer.WriteValue(message2);
var reader = new FastBufferReader(writer, Allocator.Temp); var reader = new FastBufferReader(writer, Allocator.Temp);
using (reader) using (reader)

View File

@@ -124,7 +124,7 @@ namespace Unity.Netcode.EditorTests
public void WhenNotExceedingBatchSize_NewBatchesAreNotCreated() public void WhenNotExceedingBatchSize_NewBatchesAreNotCreated()
{ {
var message = GetMessage(); var message = GetMessage();
var size = UnsafeUtility.SizeOf<TestMessage>() + UnsafeUtility.SizeOf<MessageHeader>(); var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
for (var i = 0; i < 1300 / size; ++i) for (var i = 0; i < 1300 / size; ++i)
{ {
m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients);
@@ -138,7 +138,7 @@ namespace Unity.Netcode.EditorTests
public void WhenExceedingBatchSize_NewBatchesAreCreated() public void WhenExceedingBatchSize_NewBatchesAreCreated()
{ {
var message = GetMessage(); var message = GetMessage();
var size = UnsafeUtility.SizeOf<TestMessage>() + UnsafeUtility.SizeOf<MessageHeader>(); var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
for (var i = 0; i < (1300 / size) + 1; ++i) for (var i = 0; i < (1300 / size) + 1; ++i)
{ {
m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients);
@@ -152,7 +152,7 @@ namespace Unity.Netcode.EditorTests
public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated() public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated()
{ {
var message = GetMessage(); var message = GetMessage();
var size = UnsafeUtility.SizeOf<TestMessage>() + UnsafeUtility.SizeOf<MessageHeader>(); var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
for (var i = 0; i < (1300 / size) + 1; ++i) for (var i = 0; i < (1300 / size) + 1; ++i)
{ {
m_MessagingSystem.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, m_Clients); m_MessagingSystem.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, m_Clients);
@@ -198,24 +198,25 @@ namespace Unity.Netcode.EditorTests
var reader = new FastBufferReader(m_MessageSender.MessageQueue[0], Allocator.Temp); var reader = new FastBufferReader(m_MessageSender.MessageQueue[0], Allocator.Temp);
using (reader) using (reader)
{ {
reader.TryBeginRead( reader.ReadValueSafe(out BatchHeader header);
FastBufferWriter.GetWriteSize<BatchHeader>() +
FastBufferWriter.GetWriteSize<MessageHeader>() * 2 +
FastBufferWriter.GetWriteSize<TestMessage>() * 2
);
reader.ReadValue(out BatchHeader header);
Assert.AreEqual(2, header.BatchSize); Assert.AreEqual(2, header.BatchSize);
reader.ReadValue(out MessageHeader messageHeader); MessageHeader messageHeader;
ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageType);
ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageSize);
Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader.MessageType); Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader.MessageType);
Assert.AreEqual(UnsafeUtility.SizeOf<TestMessage>(), messageHeader.MessageSize); Assert.AreEqual(UnsafeUtility.SizeOf<TestMessage>(), messageHeader.MessageSize);
reader.ReadValue(out TestMessage receivedMessage); reader.ReadValueSafe(out TestMessage receivedMessage);
Assert.AreEqual(message, receivedMessage); Assert.AreEqual(message, receivedMessage);
reader.ReadValue(out MessageHeader messageHeader2); ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageType);
Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader2.MessageType); ByteUnpacker.ReadValueBitPacked(reader, out messageHeader.MessageSize);
Assert.AreEqual(UnsafeUtility.SizeOf<TestMessage>(), messageHeader2.MessageSize);
reader.ReadValue(out TestMessage receivedMessage2); Assert.AreEqual(m_MessagingSystem.GetMessageType(typeof(TestMessage)), messageHeader.MessageType);
Assert.AreEqual(UnsafeUtility.SizeOf<TestMessage>(), messageHeader.MessageSize);
reader.ReadValueSafe(out TestMessage receivedMessage2);
Assert.AreEqual(message2, receivedMessage2); Assert.AreEqual(message2, receivedMessage2);
} }
} }

View File

@@ -0,0 +1,38 @@
using NUnit.Framework;
using UnityEngine;
using Unity.Netcode.Editor;
using UnityEngine.TestTools;
namespace Unity.Netcode.EditorTests
{
public class NetworkManagerConfigurationTests
{
/// <summary>
/// Does a simple check to make sure the nested network manager will
/// notify the user when in the editor. This is just a unit test to
/// validate this is functioning
/// </summary>
[Test]
public void NestedNetworkManagerCheck()
{
var parent = new GameObject("ParentObject");
var networkManagerObject = new GameObject(nameof(NestedNetworkManagerCheck));
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
// Make our NetworkManager's GameObject nested
networkManagerObject.transform.parent = parent.transform;
// Pre-generate the error message we are expecting to see
var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform);
// Trap for the nested NetworkManager exception
LogAssert.Expect(LogType.Error, messageToCheck);
// Since this is an in-editor test, we must force this invocation
NetworkManagerHelper.Singleton.NotifyUserOfNestedNetworkManager(networkManager, false, true);
// Clean up
Object.DestroyImmediate(parent);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a8514b4eca0c7044d9b92faf9407ec93 guid: 9b84044fccbd3cd49908f0efd5719347
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -9,7 +9,7 @@ namespace Unity.Netcode.EditorTests
[Test] [Test]
public void TestBasicRtt() public void TestBasicRtt()
{ {
var snapshot = new SnapshotSystem(default); var snapshot = new SnapshotSystem(null, new NetworkConfig(), null);
var client1 = snapshot.GetConnectionRtt(0); var client1 = snapshot.GetConnectionRtt(0);
client1.NotifySend(0, 0.0); client1.NotifySend(0, 0.0);
@@ -40,7 +40,7 @@ namespace Unity.Netcode.EditorTests
[Test] [Test]
public void TestEdgeCasesRtt() public void TestEdgeCasesRtt()
{ {
var snapshot = new SnapshotSystem(NetworkManager.Singleton); var snapshot = new SnapshotSystem(null, new NetworkConfig(), null);
var client1 = snapshot.GetConnectionRtt(0); var client1 = snapshot.GetConnectionRtt(0);
var iterationCount = NetworkConfig.RttWindowSize * 3; var iterationCount = NetworkConfig.RttWindowSize * 3;
var extraCount = NetworkConfig.RttWindowSize * 2; var extraCount = NetworkConfig.RttWindowSize * 2;

View File

@@ -0,0 +1,372 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using NUnit.Framework;
using Random = System.Random;
namespace Unity.Netcode.EditorTests
{
public class SnapshotTests
{
private SnapshotSystem m_SendSnapshot;
private SnapshotSystem m_RecvSnapshot;
private NetworkTimeSystem m_SendTimeSystem;
private NetworkTickSystem m_SendTickSystem;
private NetworkTimeSystem m_RecvTimeSystem;
private NetworkTickSystem m_RecvTickSystem;
private int m_SpawnedObjectCount;
private int m_DespawnedObjectCount;
private int m_NextSequence;
private uint m_TicksPerSec = 15;
private int m_MinSpawns;
private int m_MinDespawns;
private bool m_ExpectSpawns;
private bool m_ExpectDespawns;
private bool m_LoseNextMessage;
private bool m_PassBackResponses;
public void Prepare()
{
PrepareSendSideSnapshot();
PrepareRecvSideSnapshot();
}
public void AdvanceOneTickSendSide()
{
m_SendTimeSystem.Advance(1.0f / m_TicksPerSec);
m_SendTickSystem.UpdateTick(m_SendTimeSystem.LocalTime, m_SendTimeSystem.ServerTime);
m_SendSnapshot.NetworkUpdate(NetworkUpdateStage.EarlyUpdate);
}
public void AdvanceOneTickRecvSide()
{
m_RecvTimeSystem.Advance(1.0f / m_TicksPerSec);
m_RecvTickSystem.UpdateTick(m_RecvTimeSystem.LocalTime, m_RecvTimeSystem.ServerTime);
m_RecvSnapshot.NetworkUpdate(NetworkUpdateStage.EarlyUpdate);
}
public void AdvanceOneTick()
{
AdvanceOneTickSendSide();
AdvanceOneTickRecvSide();
}
internal int SpawnObject(SnapshotSpawnCommand command)
{
m_SpawnedObjectCount++;
return 0;
}
internal int DespawnObject(SnapshotDespawnCommand command)
{
m_DespawnedObjectCount++;
return 0;
}
internal int SendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId)
{
if (!m_PassBackResponses)
{
// we're not ack'ing anything, so those should stay 0
Debug.Assert(message.Ack.LastReceivedSequence == 0);
}
Debug.Assert(message.Ack.ReceivedSequenceMask == 0);
Debug.Assert(message.Sequence == m_NextSequence); // sequence has to be the expected one
if (m_ExpectSpawns)
{
Debug.Assert(message.Spawns.Length >= m_MinSpawns); // there has to be multiple spawns per SnapshotMessage
}
else
{
Debug.Assert(message.Spawns.Length == 0); // Spawns were not expected
}
if (m_ExpectDespawns)
{
Debug.Assert(message.Despawns.Length >= m_MinDespawns); // there has to be multiple despawns per SnapshotMessage
}
else
{
Debug.Assert(message.Despawns.IsEmpty); // this test should not have despawns
}
Debug.Assert(message.Entries.Length == 0);
m_NextSequence++;
if (!m_LoseNextMessage)
{
using var writer = new FastBufferWriter(1024, Allocator.Temp);
message.Serialize(writer);
using var reader = new FastBufferReader(writer, Allocator.Temp);
var context = new NetworkContext { SenderId = 0, Timestamp = 0.0f, SystemOwner = new Tuple<SnapshotSystem, ulong>(m_RecvSnapshot, 0) };
SnapshotDataMessage.Receive(reader, context);
}
else
{
message.Spawns.Dispose();
message.Despawns.Dispose();
message.Entries.Dispose();
}
return 0;
}
internal int SendMessageRecvSide(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId)
{
if (m_PassBackResponses)
{
using var writer = new FastBufferWriter(1024, Allocator.Temp);
message.Serialize(writer);
using var reader = new FastBufferReader(writer, Allocator.Temp);
var context = new NetworkContext { SenderId = 0, Timestamp = 0.0f, SystemOwner = new Tuple<SnapshotSystem, ulong>(m_SendSnapshot, 1) };
SnapshotDataMessage.Receive(reader, context);
}
else
{
message.Spawns.Dispose();
message.Despawns.Dispose();
message.Entries.Dispose();
}
return 0;
}
private void PrepareSendSideSnapshot()
{
var config = new NetworkConfig();
m_SendTickSystem = new NetworkTickSystem(m_TicksPerSec, 0.0, 0.0);
m_SendTimeSystem = new NetworkTimeSystem(0.2, 0.2, 1.0);
config.UseSnapshotDelta = false;
config.UseSnapshotSpawn = true;
m_SendSnapshot = new SnapshotSystem(null, config, m_SendTickSystem);
m_SendSnapshot.IsServer = true;
m_SendSnapshot.IsConnectedClient = false;
m_SendSnapshot.ServerClientId = 0;
m_SendSnapshot.ConnectedClientsId.Clear();
m_SendSnapshot.ConnectedClientsId.Add(0);
m_SendSnapshot.ConnectedClientsId.Add(1);
m_SendSnapshot.MockSendMessage = SendMessage;
m_SendSnapshot.MockSpawnObject = SpawnObject;
m_SendSnapshot.MockDespawnObject = DespawnObject;
}
private void PrepareRecvSideSnapshot()
{
var config = new NetworkConfig();
m_RecvTickSystem = new NetworkTickSystem(m_TicksPerSec, 0.0, 0.0);
m_RecvTimeSystem = new NetworkTimeSystem(0.2, 0.2, 1.0);
config.UseSnapshotDelta = false;
config.UseSnapshotSpawn = true;
m_RecvSnapshot = new SnapshotSystem(null, config, m_RecvTickSystem);
m_RecvSnapshot.IsServer = false;
m_RecvSnapshot.IsConnectedClient = true;
m_RecvSnapshot.ServerClientId = 0;
m_RecvSnapshot.ConnectedClientsId.Clear();
m_SendSnapshot.ConnectedClientsId.Add(0);
m_SendSnapshot.ConnectedClientsId.Add(1);
m_RecvSnapshot.MockSendMessage = SendMessageRecvSide;
m_RecvSnapshot.MockSpawnObject = SpawnObject;
m_RecvSnapshot.MockDespawnObject = DespawnObject;
}
private void SendSpawnToSnapshot(ulong objectId)
{
SnapshotSpawnCommand command = default;
// identity
command.NetworkObjectId = objectId;
// archetype
command.GlobalObjectIdHash = 0;
command.IsSceneObject = true;
// parameters
command.IsPlayerObject = false;
command.OwnerClientId = 0;
command.ParentNetworkId = 0;
command.ObjectPosition = default;
command.ObjectRotation = default;
command.ObjectScale = new Vector3(1.0f, 1.0f, 1.0f);
command.TargetClientIds = new List<ulong> { 1 };
m_SendSnapshot.Spawn(command);
}
private void SendDespawnToSnapshot(ulong objectId)
{
SnapshotDespawnCommand command = default;
// identity
command.NetworkObjectId = objectId;
command.TargetClientIds = new List<ulong> { 1 };
m_SendSnapshot.Despawn(command);
}
[Test]
public void TestSnapshotSpawn()
{
Prepare();
m_SpawnedObjectCount = 0;
m_NextSequence = 0;
m_ExpectSpawns = true;
m_ExpectDespawns = false;
m_MinSpawns = 2; // many spawns are to be sent together
m_LoseNextMessage = false;
m_PassBackResponses = false;
var ticksToRun = 20;
// spawns one more than current buffer size
var objectsToSpawn = m_SendSnapshot.SpawnsBufferCount + 1;
for (int i = 0; i < objectsToSpawn; i++)
{
SendSpawnToSnapshot((ulong)i);
}
for (int i = 0; i < ticksToRun; i++)
{
AdvanceOneTick();
}
Debug.Assert(m_SpawnedObjectCount == objectsToSpawn);
Debug.Assert(m_SendSnapshot.SpawnsBufferCount > objectsToSpawn); // spawn buffer should have grown
}
[Test]
public void TestSnapshotSpawnDespawns()
{
Prepare();
// test that buffers actually shrink and will grow back to needed size
m_SendSnapshot.ReduceBufferUsage();
m_RecvSnapshot.ReduceBufferUsage();
Debug.Assert(m_SendSnapshot.SpawnsBufferCount == 1);
Debug.Assert(m_SendSnapshot.DespawnsBufferCount == 1);
Debug.Assert(m_RecvSnapshot.SpawnsBufferCount == 1);
Debug.Assert(m_RecvSnapshot.DespawnsBufferCount == 1);
m_SpawnedObjectCount = 0;
m_DespawnedObjectCount = 0;
m_NextSequence = 0;
m_ExpectSpawns = true;
m_ExpectDespawns = false;
m_MinDespawns = 2; // many despawns are to be sent together
m_LoseNextMessage = false;
m_PassBackResponses = false;
var ticksToRun = 20;
// spawns one more than current buffer size
var objectsToSpawn = 10;
for (int i = 0; i < objectsToSpawn; i++)
{
SendSpawnToSnapshot((ulong)i);
}
for (int i = 0; i < ticksToRun; i++)
{
AdvanceOneTick();
}
for (int i = 0; i < objectsToSpawn; i++)
{
SendDespawnToSnapshot((ulong)i);
}
m_ExpectSpawns = true; // the un'acked spawns will still be present
m_MinSpawns = 1; // but we don't really care how they are grouped then
m_ExpectDespawns = true;
for (int i = 0; i < ticksToRun; i++)
{
AdvanceOneTick();
}
Debug.Assert(m_DespawnedObjectCount == objectsToSpawn);
}
[Test]
public void TestSnapshotMessageLoss()
{
var r = new Random();
Prepare();
m_SpawnedObjectCount = 0;
m_NextSequence = 0;
m_ExpectSpawns = true;
m_ExpectDespawns = false;
m_MinSpawns = 1;
m_LoseNextMessage = false;
m_PassBackResponses = false;
var ticksToRun = 10;
for (int i = 0; i < ticksToRun; i++)
{
m_LoseNextMessage = (r.Next() % 2) > 0;
SendSpawnToSnapshot((ulong)i);
AdvanceOneTick();
}
m_LoseNextMessage = false;
AdvanceOneTick();
AdvanceOneTick();
Debug.Assert(m_SpawnedObjectCount == ticksToRun);
}
[Test]
public void TestSnapshotAcks()
{
Prepare();
m_SpawnedObjectCount = 0;
m_NextSequence = 0;
m_ExpectSpawns = true;
m_ExpectDespawns = false;
m_MinSpawns = 1;
m_LoseNextMessage = false;
m_PassBackResponses = true;
var objectsToSpawn = 10;
for (int i = 0; i < objectsToSpawn; i++)
{
SendSpawnToSnapshot((ulong)i);
}
AdvanceOneTickSendSide(); // let's tick the send multiple time, to check it still tries to send
AdvanceOneTick();
m_ExpectSpawns = false; // all spawns should have made it back and forth and be absent from next messages
AdvanceOneTick();
for (int i = 0; i < objectsToSpawn; i++)
{
SendDespawnToSnapshot((ulong)i);
}
m_ExpectDespawns = true; // we should now be seeing despawns
AdvanceOneTickSendSide(); // let's tick the send multiple time, to check it still tries to send
AdvanceOneTick();
Debug.Assert(m_SpawnedObjectCount == objectsToSpawn);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: e946a6fdcfcb9dd48b76b38871c0a77b guid: 3d41788be1de34b7c8bcfce6a2877754
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -45,13 +45,26 @@ namespace Unity.Netcode.EditorTests
public void TestFlagShutdown() public void TestFlagShutdown()
{ {
m_NetworkManager.StartServer(); m_NetworkManager.StartServer();
m_NetworkManager.Shutdown(); m_NetworkManager.ShutdownInternal();
Assert.False(m_NetworkManager.IsServer); Assert.False(m_NetworkManager.IsServer);
Assert.False(m_NetworkManager.IsClient); Assert.False(m_NetworkManager.IsClient);
Assert.False(m_NetworkManager.IsHost); Assert.False(m_NetworkManager.IsHost);
} }
[Test]
public void TestShutdownWithoutStartForExceptions()
{
m_NetworkManager.ShutdownInternal();
}
[Test]
public void TestShutdownWithoutConfigForExceptions()
{
m_NetworkManager.NetworkConfig = null;
m_NetworkManager.ShutdownInternal();
}
[TearDown] [TearDown]
public void Teardown() public void Teardown()
{ {

View File

@@ -198,6 +198,39 @@ namespace Unity.Netcode.EditorTests
Assert.IsTrue(Approximately(startTime.Time, (startTime2 - dif).Time, maxAcceptableTotalOffset)); Assert.IsTrue(Approximately(startTime.Time, (startTime2 - dif).Time, maxAcceptableTotalOffset));
} }
[Test]
public void NetworkTickAdvanceTest()
{
var shortSteps = Enumerable.Repeat(1 / 30f, 1000);
NetworkTickAdvanceTestInternal(shortSteps, 30, 0.0f, 0.0f);
}
private NetworkTickSystem m_TickSystem;
private NetworkTimeSystem m_TimeSystem;
private int m_PreviousTick;
private void NetworkTickAdvanceTestInternal(IEnumerable<float> steps, uint tickRate, float start, float start2 = 0f)
{
m_PreviousTick = 0;
m_TickSystem = new NetworkTickSystem(tickRate, start, start2);
m_TimeSystem = NetworkTimeSystem.ServerTimeSystem();
m_TickSystem.Tick += TickUpdate;
foreach (var step in steps)
{
m_TimeSystem.Advance(step);
m_TickSystem.UpdateTick(m_TimeSystem.LocalTime, m_TimeSystem.ServerTime);
}
}
private void TickUpdate()
{
// Make sure our tick is precisely 1 + m_PreviousTick
Assert.IsTrue(m_TickSystem.LocalTime.Tick == m_PreviousTick + 1);
// Assign the m_PreviousTick value for next tick check
m_PreviousTick = m_TickSystem.LocalTime.Tick;
}
private static bool Approximately(double a, double b, double epsilon = 0.000001d) private static bool Approximately(double a, double b, double epsilon = 0.000001d)
{ {
var dif = Math.Abs(a - b); var dif = Math.Abs(a - b);

View File

@@ -2,6 +2,7 @@
"name": "Unity.Netcode.EditorTests", "name": "Unity.Netcode.EditorTests",
"rootNamespace": "Unity.Netcode.EditorTests", "rootNamespace": "Unity.Netcode.EditorTests",
"references": [ "references": [
"Unity.Collections",
"Unity.Netcode.Runtime", "Unity.Netcode.Runtime",
"Unity.Netcode.Editor", "Unity.Netcode.Editor",
"Unity.Netcode.Components", "Unity.Netcode.Components",

View File

@@ -14,9 +14,11 @@ namespace Unity.Netcode.RuntimeTests.Metrics
{ {
public class MessagingMetricsTests : DualClientMetricTestBase public class MessagingMetricsTests : DualClientMetricTestBase
{ {
const uint MessageNameHashSize = 8; private const uint k_MessageNameHashSize = 8;
// Header is dynamically sized due to packing, will be 2 bytes for all test messages.
const uint MessageOverhead = MessageNameHashSize; private const int k_MessageHeaderSize = 2;
private static readonly int k_NamedMessageOverhead = (int)k_MessageNameHashSize + k_MessageHeaderSize;
private static readonly int k_UnnamedMessageOverhead = k_MessageHeaderSize;
protected override int NbClients => 2; protected override int NbClients => 2;
@@ -111,7 +113,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var namedMessageSent = namedMessageSentMetricValues.First(); var namedMessageSent = namedMessageSentMetricValues.First();
Assert.AreEqual(messageName.ToString(), namedMessageSent.Name); Assert.AreEqual(messageName.ToString(), namedMessageSent.Name);
Assert.AreEqual(FirstClient.LocalClientId, namedMessageSent.Connection.Id); Assert.AreEqual(FirstClient.LocalClientId, namedMessageSent.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, namedMessageSent.BytesCount); Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + k_NamedMessageOverhead, namedMessageSent.BytesCount);
} }
[UnityTest] [UnityTest]
@@ -132,7 +134,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var namedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); var namedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, namedMessageSentMetricValues.Count); Assert.AreEqual(2, namedMessageSentMetricValues.Count);
Assert.That(namedMessageSentMetricValues.Select(x => x.Name), Has.All.EqualTo(messageName.ToString())); Assert.That(namedMessageSentMetricValues.Select(x => x.Name), Has.All.EqualTo(messageName.ToString()));
Assert.That(namedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead)); Assert.That(namedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(messageName) + k_NamedMessageOverhead));
} }
[UnityTest] [UnityTest]
@@ -181,7 +183,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var namedMessageReceived = namedMessageReceivedValues.First(); var namedMessageReceived = namedMessageReceivedValues.First();
Assert.AreEqual(messageName.ToString(), namedMessageReceived.Name); Assert.AreEqual(messageName.ToString(), namedMessageReceived.Name);
Assert.AreEqual(Server.LocalClientId, namedMessageReceived.Connection.Id); Assert.AreEqual(Server.LocalClientId, namedMessageReceived.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, namedMessageReceived.BytesCount); Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + k_NamedMessageOverhead, namedMessageReceived.BytesCount);
} }
[UnityTest] [UnityTest]
@@ -205,7 +207,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var unnamedMessageSent = unnamedMessageSentMetricValues.First(); var unnamedMessageSent = unnamedMessageSentMetricValues.First();
Assert.AreEqual(FirstClient.LocalClientId, unnamedMessageSent.Connection.Id); Assert.AreEqual(FirstClient.LocalClientId, unnamedMessageSent.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(message), unnamedMessageSent.BytesCount); Assert.AreEqual(FastBufferWriter.GetWriteSize(message) + k_UnnamedMessageOverhead, unnamedMessageSent.BytesCount);
} }
[UnityTest] [UnityTest]
@@ -225,7 +227,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var unnamedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); var unnamedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, unnamedMessageSentMetricValues.Count); Assert.AreEqual(2, unnamedMessageSentMetricValues.Count);
Assert.That(unnamedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(message))); Assert.That(unnamedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(message) + k_UnnamedMessageOverhead));
var clientIds = unnamedMessageSentMetricValues.Select(x => x.Connection.Id).ToList(); var clientIds = unnamedMessageSentMetricValues.Select(x => x.Connection.Id).ToList();
Assert.Contains(FirstClient.LocalClientId, clientIds); Assert.Contains(FirstClient.LocalClientId, clientIds);
@@ -268,7 +270,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var unnamedMessageReceived = unnamedMessageReceivedValues.First(); var unnamedMessageReceived = unnamedMessageReceivedValues.First();
Assert.AreEqual(Server.LocalClientId, unnamedMessageReceived.Connection.Id); Assert.AreEqual(Server.LocalClientId, unnamedMessageReceived.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(message), unnamedMessageReceived.BytesCount); Assert.AreEqual(FastBufferWriter.GetWriteSize(message) + k_UnnamedMessageOverhead, unnamedMessageReceived.BytesCount);
} }
} }
} }

View File

@@ -14,6 +14,8 @@ namespace Unity.Netcode.RuntimeTests.Metrics
{ {
private const string k_NewNetworkObjectName = "TestNetworkObjectToSpawn"; private const string k_NewNetworkObjectName = "TestNetworkObjectToSpawn";
private NetworkObject m_NewNetworkPrefab; private NetworkObject m_NewNetworkPrefab;
// Header is dynamically sized due to packing, will be 2 bytes for all test messages.
private const int k_MessageHeaderSize = 2;
protected override Action<GameObject> UpdatePlayerPrefab => _ => protected override Action<GameObject> UpdatePlayerPrefab => _ =>
{ {
@@ -58,7 +60,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var ownershipChangeSent = metricValues.First(); var ownershipChangeSent = metricValues.First();
Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId); Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId);
Assert.AreEqual(Server.LocalClientId, ownershipChangeSent.Connection.Id); Assert.AreEqual(Server.LocalClientId, ownershipChangeSent.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize<ChangeOwnershipMessage>(), ownershipChangeSent.BytesCount); Assert.AreEqual(FastBufferWriter.GetWriteSize<ChangeOwnershipMessage>() + k_MessageHeaderSize, ownershipChangeSent.BytesCount);
} }
[UnityTest] [UnityTest]

View File

@@ -11,6 +11,11 @@ namespace Unity.Netcode.RuntimeTests.Metrics
{ {
internal class ServerLogsMetricTests : SingleClientMetricTestBase internal class ServerLogsMetricTests : SingleClientMetricTestBase
{ {
// Header is dynamically sized due to packing, will be 2 bytes for all test messages.
private const int k_MessageHeaderSize = 2;
private static readonly int k_ServerLogSentMessageOverhead = 2 + k_MessageHeaderSize;
private static readonly int k_ServerLogReceivedMessageOverhead = 2;
[UnityTest] [UnityTest]
public IEnumerator TrackServerLogSentMetric() public IEnumerator TrackServerLogSentMetric()
{ {
@@ -27,7 +32,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var sentMetric = sentMetrics.First(); var sentMetric = sentMetrics.First();
Assert.AreEqual(Server.LocalClientId, sentMetric.Connection.Id); Assert.AreEqual(Server.LocalClientId, sentMetric.Connection.Id);
Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)sentMetric.LogLevel); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)sentMetric.LogLevel);
Assert.AreEqual(message.Length + 2, sentMetric.BytesCount); Assert.AreEqual(message.Length + k_ServerLogSentMessageOverhead, sentMetric.BytesCount);
} }
[UnityTest] [UnityTest]
@@ -46,7 +51,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var receivedMetric = receivedMetrics.First(); var receivedMetric = receivedMetrics.First();
Assert.AreEqual(Client.LocalClientId, receivedMetric.Connection.Id); Assert.AreEqual(Client.LocalClientId, receivedMetric.Connection.Id);
Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)receivedMetric.LogLevel); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)receivedMetric.LogLevel);
Assert.AreEqual(message.Length + 2, receivedMetric.BytesCount); Assert.AreEqual(message.Length + k_ServerLogReceivedMessageOverhead, receivedMetric.BytesCount);
} }
} }
} }

View File

@@ -13,7 +13,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics
{ {
internal class TransportBytesMetricsTests : SingleClientMetricTestBase internal class TransportBytesMetricsTests : SingleClientMetricTestBase
{ {
static readonly long MessageOverhead = 8 + FastBufferWriter.GetWriteSize<BatchHeader>() + FastBufferWriter.GetWriteSize<MessageHeader>(); // Header is dynamically sized due to packing, will be 2 bytes for all test messages.
private const int k_MessageHeaderSize = 2;
static readonly long MessageOverhead = 8 + FastBufferWriter.GetWriteSize<BatchHeader>() + k_MessageHeaderSize;
[UnityTest] [UnityTest]
public IEnumerator TrackTotalNumberOfBytesSent() public IEnumerator TrackTotalNumberOfBytesSent()

View File

@@ -450,7 +450,7 @@ namespace Unity.Netcode.RuntimeTests
if (!waitResult.Result) if (!waitResult.Result)
{ {
throw new Exception(); Assert.Fail("Predicate condition failed");
} }
} }

View File

@@ -0,0 +1,37 @@
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode;
using Unity.Netcode.RuntimeTests;
using Object = UnityEngine.Object;
namespace TestProject.RuntimeTests
{
public class NestedNetworkManagerTests
{
[UnityTest]
public IEnumerator CheckNestedNetworkManager()
{
var parent = new GameObject("ParentObject");
var networkManagerObject = new GameObject(nameof(CheckNestedNetworkManager));
// Make our NetworkManager's GameObject nested
networkManagerObject.transform.parent = parent.transform;
// Pre-generate the error message we are expecting to see
var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform);
var transport = networkManagerObject.AddComponent<SIPTransport>();
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
networkManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
// Trap for the nested NetworkManager exception
LogAssert.Expect(LogType.Error, messageToCheck);
yield return new WaitForSeconds(0.02f);
// Clean up
Object.Destroy(parent);
yield return null;
}
}
}

View File

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

View File

@@ -103,6 +103,8 @@ namespace Unity.Netcode.RuntimeTests
var serverNetVarCount = serverNetVarsToUpdate.Count; var serverNetVarCount = serverNetVarsToUpdate.Count;
yield return new WaitForSeconds(0); // wait a frame to make sure spawn is done yield return new WaitForSeconds(0); // wait a frame to make sure spawn is done
// todo: with Snapshot spawns enabled and the current race condition, the following line is needed:
// yield return new WaitForSeconds(0.2f); // wait a bit to fix the spawn/update race condition
foreach (var netVar in serverNetVarsToUpdate) foreach (var netVar in serverNetVarsToUpdate)
{ {

View File

@@ -0,0 +1,132 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Tests that check OnNetworkDespawn being invoked
/// </summary>
public class NetworkObjectOnNetworkDespawnTests
{
private NetworkManager m_ServerHost;
private NetworkManager[] m_Clients;
private GameObject m_ObjectToSpawn;
private NetworkObject m_NetworkObject;
internal class OnNetworkDespawnTestComponent : NetworkBehaviour
{
public bool OnNetworkDespawnCalled { get; internal set; }
public override void OnNetworkSpawn()
{
OnNetworkDespawnCalled = false;
base.OnNetworkSpawn();
}
public override void OnNetworkDespawn()
{
OnNetworkDespawnCalled = true;
base.OnNetworkDespawn();
}
}
[UnitySetUp]
public IEnumerator Setup()
{
Assert.IsTrue(MultiInstanceHelpers.Create(1, out m_ServerHost, out m_Clients));
m_ObjectToSpawn = new GameObject();
m_NetworkObject = m_ObjectToSpawn.AddComponent<NetworkObject>();
m_ObjectToSpawn.AddComponent<OnNetworkDespawnTestComponent>();
// Make it a prefab
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_NetworkObject);
var networkPrefab = new NetworkPrefab();
networkPrefab.Prefab = m_ObjectToSpawn;
m_ServerHost.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
foreach (var client in m_Clients)
{
client.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
}
yield return null;
}
[UnityTearDown]
public IEnumerator Teardown()
{
// Shutdown and clean up both of our NetworkManager instances
if (m_ObjectToSpawn)
{
Object.Destroy(m_ObjectToSpawn);
m_ObjectToSpawn = null;
}
MultiInstanceHelpers.Destroy();
yield return null;
}
public enum InstanceType
{
Server,
Host,
Client
}
/// <summary>
/// Tests that a spawned NetworkObject's associated NetworkBehaviours will have
/// their OnNetworkDespawn invoked during NetworkManager shutdown.
/// </summary>
[UnityTest]
public IEnumerator TestNetworkObjectDespawnOnShutdown([Values(InstanceType.Server, InstanceType.Host, InstanceType.Client)] InstanceType despawnCheck)
{
var useHost = despawnCheck == InstanceType.Server ? false : true;
var networkManager = despawnCheck == InstanceType.Host || despawnCheck == InstanceType.Server ? m_ServerHost : m_Clients[0];
// Start the instances
if (!MultiInstanceHelpers.Start(useHost, m_ServerHost, m_Clients))
{
Debug.LogError("Failed to start instances");
Assert.Fail("Failed to start instances");
}
// [Client-Side] Wait for a connection to the server
yield return MultiInstanceHelpers.WaitForClientsConnected(m_Clients, null, 512);
// [Host-Server-Side] Check to make sure all clients are connected
var clientCount = useHost ? m_Clients.Length + 1 : m_Clients.Length;
yield return MultiInstanceHelpers.WaitForClientsConnectedToServer(m_ServerHost, clientCount, null, 512);
// Spawn the test object
var spawnedObject = Object.Instantiate(m_NetworkObject);
var spawnedNetworkObject = spawnedObject.GetComponent<NetworkObject>();
spawnedNetworkObject.NetworkManagerOwner = m_ServerHost;
spawnedNetworkObject.Spawn(true);
// Get the spawned object relative to which NetworkManager instance we are testing.
var relativeSpawnedObject = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.GetComponent<OnNetworkDespawnTestComponent>() != null), networkManager, relativeSpawnedObject));
var onNetworkDespawnTestComponent = relativeSpawnedObject.Result.GetComponent<OnNetworkDespawnTestComponent>();
// Confirm it is not set before shutting down the NetworkManager
Assert.IsFalse(onNetworkDespawnTestComponent.OnNetworkDespawnCalled);
// Shutdown the NetworkManager instance we are testing.
networkManager.Shutdown();
// Since shutdown is now delayed until the post frame update
// just wait 2 frames before checking to see if OnNetworkDespawnCalled is true
var currentFrame = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount <= currentFrame);
// Confirm that OnNetworkDespawn is invoked after shutdown
Assert.IsTrue(onNetworkDespawnTestComponent.OnNetworkDespawnCalled);
}
}
}

View File

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

View File

@@ -115,6 +115,9 @@ namespace Unity.Netcode.RuntimeTests
Assert.That(clientNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(dummyNetworkObjectId)); Assert.That(clientNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(dummyNetworkObjectId));
} }
// Verifies that removing the ownership when the default (server) is already set does not cause
// a Key Not Found Exception
m_ServerNetworkManager.SpawnManager.RemoveOwnership(dummyNetworkObject);
var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[dummyNetworkObjectId]; var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[dummyNetworkObjectId];
var clientObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[dummyNetworkObjectId]; var clientObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[dummyNetworkObjectId];

View File

@@ -11,9 +11,19 @@ namespace Unity.Netcode.RuntimeTests
{ {
public uint SomeInt; public uint SomeInt;
public bool SomeBool; public bool SomeBool;
public static bool NetworkSerializeCalledOnWrite;
public static bool NetworkSerializeCalledOnRead;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{ {
if (serializer.IsReader)
{
NetworkSerializeCalledOnRead = true;
}
else
{
NetworkSerializeCalledOnWrite = true;
}
serializer.SerializeValue(ref SomeInt); serializer.SerializeValue(ref SomeInt);
serializer.SerializeValue(ref SomeBool); serializer.SerializeValue(ref SomeBool);
} }
@@ -214,8 +224,8 @@ namespace Unity.Netcode.RuntimeTests
{ {
return m_Player1OnServer.TheList.Count == 1 && return m_Player1OnServer.TheList.Count == 1 &&
m_Player1OnClient1.TheList.Count == 1 && m_Player1OnClient1.TheList.Count == 1 &&
m_Player1OnServer.TheList.Contains(k_TestKey1) && m_Player1OnServer.TheList.Contains(k_TestVal1) &&
m_Player1OnClient1.TheList.Contains(k_TestKey1); m_Player1OnClient1.TheList.Contains(k_TestVal1);
} }
); );
} }
@@ -318,6 +328,58 @@ namespace Unity.Netcode.RuntimeTests
); );
} }
[UnityTest]
public IEnumerator NetworkListValueUpdate([Values(true, false)] bool useHost)
{
m_TestWithHost = useHost;
yield return MultiInstanceHelpers.RunAndWaitForCondition(
() =>
{
m_Player1OnServer.TheList.Add(k_TestVal1);
},
() =>
{
return m_Player1OnServer.TheList.Count == 1 &&
m_Player1OnClient1.TheList.Count == 1 &&
m_Player1OnServer.TheList[0] == k_TestVal1 &&
m_Player1OnClient1.TheList[0] == k_TestVal1;
}
);
var testSucceeded = false;
void TestValueUpdatedCallback(NetworkListEvent<int> changedEvent)
{
testSucceeded = changedEvent.PreviousValue == k_TestVal1 &&
changedEvent.Value == k_TestVal3;
}
try
{
yield return MultiInstanceHelpers.RunAndWaitForCondition(
() =>
{
m_Player1OnServer.TheList[0] = k_TestVal3;
m_Player1OnClient1.TheList.OnListChanged += TestValueUpdatedCallback;
},
() =>
{
return m_Player1OnServer.TheList.Count == 1 &&
m_Player1OnClient1.TheList.Count == 1 &&
m_Player1OnServer.TheList[0] == k_TestVal3 &&
m_Player1OnClient1.TheList[0] == k_TestVal3;
}
);
}
finally
{
m_Player1OnClient1.TheList.OnListChanged -= TestValueUpdatedCallback;
}
Assert.That(testSucceeded);
}
[Test] [Test]
public void NetworkListIEnumerator([Values(true, false)] bool useHost) public void NetworkListIEnumerator([Values(true, false)] bool useHost)
{ {
@@ -409,6 +471,28 @@ namespace Unity.Netcode.RuntimeTests
); );
} }
[UnityTest]
public IEnumerator TestINetworkSerializableCallsNetworkSerialize([Values(true, false)] bool useHost)
{
m_TestWithHost = useHost;
yield return MultiInstanceHelpers.RunAndWaitForCondition(
() =>
{
TestStruct.NetworkSerializeCalledOnWrite = false;
TestStruct.NetworkSerializeCalledOnRead = false;
m_Player1OnServer.TheStruct.Value =
new TestStruct() { SomeInt = k_TestUInt, SomeBool = false };
m_Player1OnServer.TheStruct.SetDirty(true);
},
() =>
{
return
TestStruct.NetworkSerializeCalledOnWrite &&
TestStruct.NetworkSerializeCalledOnRead;
}
);
}
[UnityTearDown] [UnityTearDown]
public override IEnumerator Teardown() public override IEnumerator Teardown()
{ {

View File

@@ -70,6 +70,7 @@ namespace Unity.Netcode.RuntimeTests.Physics
yield return NetworkRigidbodyTestBase.WaitForFrames(5); yield return NetworkRigidbodyTestBase.WaitForFrames(5);
// This should equal Kinematic
Assert.IsTrue(serverPlayer.GetComponent<Rigidbody2D>().isKinematic == Kinematic); Assert.IsTrue(serverPlayer.GetComponent<Rigidbody2D>().isKinematic == Kinematic);
yield return NetworkRigidbodyTestBase.WaitForFrames(5); yield return NetworkRigidbodyTestBase.WaitForFrames(5);

View File

@@ -1,148 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Used in conjunction with the RpcQueueTest to validate:
/// - Sending and Receiving pipeline to validate that both sending and receiving pipelines are functioning properly.
/// - Usage of the ServerRpcParams.Send.UpdateStage and ClientRpcParams.Send.UpdateStage functionality.
/// - Rpcs receive will be invoked at the appropriate NetworkUpdateStage.
/// </summary>
public class RpcPipelineTestComponent : NetworkBehaviour
{
/// <summary>
/// Allows the external RPCQueueTest to begin testing or stop it
/// </summary>
public bool PingSelfEnabled;
/// <summary>
/// How many times will we iterate through the various NetworkUpdateStage values?
/// (defaults to 2)
/// </summary>
public int MaxIterations = 2;
// Start is called before the first frame update
private void Start()
{
m_MaxStagesSent = Enum.GetValues(typeof(NetworkUpdateStage)).Length * MaxIterations;
//Start out with this being true (for first sequence)
m_ClientReceivedRpc = true;
}
/// <summary>
/// Determine if we have iterated over more than our maximum stages we want to test
/// </summary>
/// <returns>true or false (did we exceed the max iterations or not?)</returns>
public bool ExceededMaxIterations()
{
if (m_StagesSent.Count > m_MaxStagesSent && m_MaxStagesSent > 0)
{
return true;
}
return false;
}
/// <summary>
/// Returns back whether the test has completed the total number of iterations
/// </summary>
/// <returns></returns>
public bool IsTestComplete()
{
if (m_Counter >= MaxIterations)
{
return true;
}
return false;
}
private bool m_ClientReceivedRpc;
private int m_Counter = 0;
private int m_MaxStagesSent = 0;
private ServerRpcParams m_ServerParams;
private ClientRpcParams m_ClientParams;
private NetworkUpdateStage m_LastUpdateStage;
// Update is called once per frame
private void Update()
{
if (NetworkManager.Singleton.IsListening && PingSelfEnabled && m_ClientReceivedRpc)
{
//Reset this for the next sequence of rpcs
m_ClientReceivedRpc = false;
//As long as testing isn't completed, keep testing
if (!IsTestComplete())
{
PingMySelfServerRpc(m_StagesSent.Count, m_ServerParams);
}
}
}
private readonly List<NetworkUpdateStage> m_ServerStagesReceived = new List<NetworkUpdateStage>();
private readonly List<NetworkUpdateStage> m_ClientStagesReceived = new List<NetworkUpdateStage>();
private readonly List<NetworkUpdateStage> m_StagesSent = new List<NetworkUpdateStage>();
/// <summary>
/// Assures all update stages were in alginment with one another
/// </summary>
/// <returns>true or false</returns>
public bool ValidateUpdateStages()
{
var validated = false;
if (m_ServerStagesReceived.Count == m_ClientStagesReceived.Count && m_ClientStagesReceived.Count == m_StagesSent.Count)
{
for (int i = 0; i < m_StagesSent.Count; i++)
{
var currentStage = m_StagesSent[i];
if (m_ServerStagesReceived[i] != currentStage)
{
Debug.Log($"ServerRpc Update Stage ({m_ServerStagesReceived[i]}) is not equal to the current update stage ({currentStage})");
return validated;
}
if (m_ClientStagesReceived[i] != currentStage)
{
Debug.Log($"ClientRpc Update Stage ({m_ClientStagesReceived[i]}) is not equal to the current update stage ({currentStage})");
return validated;
}
}
validated = true;
}
return validated;
}
/// <summary>
/// Server side RPC for testing
/// </summary>
/// <param name="parameters">server rpc parameters</param>
[ServerRpc]
private void PingMySelfServerRpc(int currentCount, ServerRpcParams parameters = default)
{
Debug.Log($"{nameof(PingMySelfServerRpc)}: [HostClient][ServerRpc][{currentCount}] invoked.");
PingMySelfClientRpc(currentCount, m_ClientParams);
}
/// <summary>
/// Client Side RPC called by PingMySelfServerRPC to validate both Client->Server and Server-Client pipeline is working
/// </summary>
/// <param name="parameters">client rpc parameters</param>
[ClientRpc]
private void PingMySelfClientRpc(int currentCount, ClientRpcParams parameters = default)
{
Debug.Log($"{nameof(PingMySelfClientRpc)}: [HostServer][ClientRpc][{currentCount}] invoked. (previous output line should confirm this)");
m_ClientReceivedRpc = true;
}
}
}

View File

@@ -10,13 +10,13 @@ namespace Unity.Netcode.RuntimeTests
{ {
public class RpcTestNB : NetworkBehaviour public class RpcTestNB : NetworkBehaviour
{ {
public event Action OnServer_Rpc; public event Action<ulong, ServerRpcParams> OnServer_Rpc;
public event Action OnClient_Rpc; public event Action OnClient_Rpc;
[ServerRpc] [ServerRpc]
public void MyServerRpc() public void MyServerRpc(ulong clientId, ServerRpcParams param = default)
{ {
OnServer_Rpc(); OnServer_Rpc(clientId, param);
} }
[ClientRpc] [ClientRpc]
@@ -42,11 +42,12 @@ namespace Unity.Netcode.RuntimeTests
{ {
// This is the *SERVER VERSION* of the *CLIENT PLAYER* // This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>(); var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); var clientId = m_ClientNetworkManagers[0].LocalClientId;
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == clientId), m_ServerNetworkManager, serverClientPlayerResult));
// This is the *CLIENT VERSION* of the *CLIENT PLAYER* // This is the *CLIENT VERSION* of the *CLIENT PLAYER*
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>(); var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == clientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
// Setup state // Setup state
bool hasReceivedServerRpc = false; bool hasReceivedServerRpc = false;
@@ -59,15 +60,16 @@ namespace Unity.Netcode.RuntimeTests
hasReceivedClientRpcRemotely = true; hasReceivedClientRpcRemotely = true;
}; };
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += () => clientClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += (clientId, param) =>
{ {
// The RPC invoked locally. (Weaver failure?) // The RPC invoked locally. (Weaver failure?)
Assert.Fail("ServerRpc invoked locally. Weaver failure?"); Assert.Fail("ServerRpc invoked locally. Weaver failure?");
}; };
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += () => serverClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += (clientId, param) =>
{ {
Debug.Log("ServerRpc received on server object"); Debug.Log("ServerRpc received on server object");
Assert.True(param.Receive.SenderClientId == clientId);
hasReceivedServerRpc = true; hasReceivedServerRpc = true;
}; };
@@ -79,7 +81,7 @@ namespace Unity.Netcode.RuntimeTests
}; };
// Send ServerRpc // Send ServerRpc
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().MyServerRpc(); clientClientPlayerResult.Result.GetComponent<RpcTestNB>().MyServerRpc(clientId);
// Send ClientRpc // Send ClientRpc
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().MyClientRpc(); serverClientPlayerResult.Result.GetComponent<RpcTestNB>().MyClientRpc();

View File

@@ -0,0 +1,73 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class StopStartRuntimeTests
{
[UnityTest]
public IEnumerator WhenShuttingDownAndRestarting_SDKRestartsSuccessfullyAndStaysRunning()
{ // create server and client instances
MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients);
try
{
// create prefab
var gameObject = new GameObject("PlayerObject");
var networkObject = gameObject.AddComponent<NetworkObject>();
networkObject.DontDestroyWithOwner = true;
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject);
server.NetworkConfig.PlayerPrefab = gameObject;
for (int i = 0; i < clients.Length; i++)
{
clients[i].NetworkConfig.PlayerPrefab = gameObject;
}
// start server and connect clients
MultiInstanceHelpers.Start(false, server, clients);
// wait for connection on client side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients));
// wait for connection on server side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(server));
// shutdown the server
server.Shutdown();
// wait 1 frame because shutdowns are delayed
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
// Verify the shutdown occurred
Assert.IsFalse(server.IsServer);
Assert.IsFalse(server.IsListening);
Assert.IsFalse(server.IsHost);
Assert.IsFalse(server.IsClient);
server.StartServer();
// Verify the server started
Assert.IsTrue(server.IsServer);
Assert.IsTrue(server.IsListening);
// Wait several frames
nextFrameNumber = Time.frameCount + 10;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
// Verify the server is still running
Assert.IsTrue(server.IsServer);
Assert.IsTrue(server.IsListening);
}
finally
{
// cleanup
MultiInstanceHelpers.Destroy();
}
}
}
}

View File

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

View File

@@ -56,8 +56,8 @@ namespace Unity.Netcode.RuntimeTests
var networkManagers = MultiInstanceHelpers.NetworkManagerInstances.ToArray(); var networkManagers = MultiInstanceHelpers.NetworkManagerInstances.ToArray();
var server = networkManagers.First(t => t.IsServer); var server = networkManagers.First(t => t.IsServer);
var firstClient = networkManagers.First(t => t.IsClient); var firstClient = networkManagers.First(t => !t.IsServer);
var secondClient = networkManagers.Last(t => t.IsClient); var secondClient = networkManagers.Last(t => !t.IsServer);
Assert.AreNotEqual(firstClient, secondClient); Assert.AreNotEqual(firstClient, secondClient);

View File

@@ -112,6 +112,7 @@ namespace Unity.Netcode.RuntimeTests
{ {
s_Server = null; s_Server = null;
m_Peers.Remove(ServerClientId); m_Peers.Remove(ServerClientId);
m_LocalConnection = null;
} }
public override void Shutdown() public override void Shutdown()

View File

@@ -2,21 +2,22 @@
"name": "com.unity.netcode.gameobjects", "name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects", "displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
"version": "1.0.0-pre.2", "version": "1.0.0-pre.5",
"unity": "2020.3", "unity": "2020.3",
"dependencies": { "dependencies": {
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.animation": "1.0.0", "com.unity.modules.animation": "1.0.0",
"com.unity.modules.physics": "1.0.0",
"com.unity.modules.physics2d": "1.0.0",
"com.unity.nuget.mono-cecil": "1.10.1", "com.unity.nuget.mono-cecil": "1.10.1",
"com.unity.collections": "1.0.0-pre.5" "com.unity.collections": "1.1.0"
}, },
"upmCi": { "upmCi": {
"footprint": "f3acafb35c17cf3cb48042bf9655c4ada00c34ae" "footprint": "770504b1f691c766a300b616c1d8106335265f3c"
}, },
"repository": { "repository": {
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
"type": "git", "type": "git",
"revision": "bcef5b992c5414707ff48c95a48a113fd0e09ad3" "revision": "d1f990d97b80d49ce12fce7357cbd5f6b794ce01"
}, },
"samples": [ "samples": [
{ {