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). ## [2.0.0-pre.3] - 2024-07-23 ### Added - Added: `UnityTransport.GetNetworkDriver` and `UnityTransport.GetLocalEndpoint` methods to expose the driver and local endpoint being used. (#2978) ### Fixed - Fixed issue where deferred despawn was causing GC allocations when converting an `IEnumerable` to a list. (#2983) - Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2979) - Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded. (#2971) - Fixed issue where `Rigidbody2d` under Unity 6000.0.11f1 has breaking changes where `velocity` is now `linearVelocity` and `isKinematic` is replaced by `bodyType`. (#2971) - Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968) - Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined.(#2962) - Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962) - Fixed issue when using a distributed authority network topology where it would allow a session owner to spawn a `NetworkObject` prior to being approved. Now, an error message is logged and the `NetworkObject` will not be spawned prior to the client being approved. (#2962) - Fixed issue where attempting to spawn during `NetworkBehaviour.OnInSceneObjectsSpawned` and `NetworkBehaviour.OnNetworkSessionSynchronized` notifications would throw a collection modified exception. (#2962) ### Changed - Changed logic where clients can now set the `NetworkSceneManager` client synchronization mode when using a distributed authority network topology. (#2985)
269 lines
13 KiB
C#
269 lines
13 KiB
C#
using System.Collections.Generic;
|
|
using Unity.Collections;
|
|
|
|
namespace Unity.Netcode
|
|
{
|
|
internal struct ConnectionApprovedMessage : INetworkMessage
|
|
{
|
|
private const int k_VersionAddClientIds = 1;
|
|
public int Version => k_VersionAddClientIds;
|
|
|
|
public ulong OwnerClientId;
|
|
public int NetworkTick;
|
|
// The cloud state service should set this if we are restoring a session
|
|
public bool IsRestoredSession;
|
|
public ulong CurrentSessionOwner;
|
|
// Not serialized
|
|
public bool IsDistributedAuthority;
|
|
|
|
// Not serialized, held as references to serialize NetworkVariable data
|
|
public HashSet<NetworkObject> SpawnedObjectsList;
|
|
|
|
private FastBufferReader m_ReceivedSceneObjectData;
|
|
|
|
public NativeArray<MessageVersionData> MessageVersions;
|
|
|
|
public NativeArray<ulong> ConnectedClientIds;
|
|
|
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
|
{
|
|
// ============================================================
|
|
// BEGIN FORBIDDEN SEGMENT
|
|
// DO NOT CHANGE THIS HEADER. Everything added to this message
|
|
// must go AFTER the message version header.
|
|
// ============================================================
|
|
BytePacker.WriteValueBitPacked(writer, MessageVersions.Length);
|
|
foreach (var messageVersion in MessageVersions)
|
|
{
|
|
messageVersion.Serialize(writer);
|
|
}
|
|
// ============================================================
|
|
// END FORBIDDEN SEGMENT
|
|
// ============================================================
|
|
|
|
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
|
BytePacker.WriteValueBitPacked(writer, NetworkTick);
|
|
if (IsDistributedAuthority)
|
|
{
|
|
writer.WriteValueSafe(IsRestoredSession);
|
|
BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner);
|
|
}
|
|
|
|
if (targetVersion >= k_VersionAddClientIds)
|
|
{
|
|
writer.WriteValueSafe(ConnectedClientIds);
|
|
}
|
|
|
|
uint sceneObjectCount = 0;
|
|
|
|
// When SpawnedObjectsList is not null then scene management is disabled. Provide a list of
|
|
// all observed and spawned NetworkObjects that the approved client needs to synchronize.
|
|
if (SpawnedObjectsList != null)
|
|
{
|
|
var pos = writer.Position;
|
|
writer.Seek(writer.Position + FastBufferWriter.GetWriteSize(sceneObjectCount));
|
|
|
|
// Serialize NetworkVariable data
|
|
foreach (var sobj in SpawnedObjectsList)
|
|
{
|
|
if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId)))
|
|
{
|
|
sobj.Observers.Add(OwnerClientId);
|
|
// In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized.
|
|
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId, IsDistributedAuthority);
|
|
sceneObject.Serialize(writer);
|
|
++sceneObjectCount;
|
|
}
|
|
}
|
|
|
|
writer.Seek(pos);
|
|
// Can't pack this value because its space is reserved, so it needs to always use all the reserved space.
|
|
writer.WriteValueSafe(sceneObjectCount);
|
|
writer.Seek(writer.Length);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteValueSafe(sceneObjectCount);
|
|
}
|
|
}
|
|
|
|
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
|
{
|
|
var networkManager = (NetworkManager)context.SystemOwner;
|
|
if (!networkManager.IsClient)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// ============================================================
|
|
// BEGIN FORBIDDEN SEGMENT
|
|
// DO NOT CHANGE THIS HEADER. Everything added to this message
|
|
// must go AFTER the message version header.
|
|
// ============================================================
|
|
ByteUnpacker.ReadValueBitPacked(reader, out int length);
|
|
var messageHashesInOrder = new NativeArray<uint>(length, Allocator.Temp);
|
|
for (var i = 0; i < length; ++i)
|
|
{
|
|
var messageVersion = new MessageVersionData();
|
|
messageVersion.Deserialize(reader);
|
|
networkManager.ConnectionManager.MessageManager.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
|
|
messageHashesInOrder[i] = messageVersion.Hash;
|
|
|
|
// Update the received version since this message will always be passed version 0, due to the map not
|
|
// being initialized until just now.
|
|
var messageType = networkManager.ConnectionManager.MessageManager.GetMessageForHash(messageVersion.Hash);
|
|
if (messageType == typeof(ConnectionApprovedMessage))
|
|
{
|
|
receivedMessageVersion = messageVersion.Version;
|
|
}
|
|
}
|
|
networkManager.ConnectionManager.MessageManager.SetServerMessageOrder(messageHashesInOrder);
|
|
messageHashesInOrder.Dispose();
|
|
// ============================================================
|
|
// END FORBIDDEN SEGMENT
|
|
// ============================================================
|
|
|
|
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
|
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
|
|
if (networkManager.DistributedAuthorityMode)
|
|
{
|
|
reader.ReadValueSafe(out IsRestoredSession);
|
|
ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner);
|
|
}
|
|
|
|
if (receivedMessageVersion >= k_VersionAddClientIds)
|
|
{
|
|
reader.ReadValueSafe(out ConnectedClientIds, Allocator.TempJob);
|
|
}
|
|
else
|
|
{
|
|
ConnectedClientIds = new NativeArray<ulong>(0, Allocator.TempJob);
|
|
}
|
|
|
|
m_ReceivedSceneObjectData = reader;
|
|
return true;
|
|
}
|
|
|
|
public void Handle(ref NetworkContext context)
|
|
{
|
|
var networkManager = (NetworkManager)context.SystemOwner;
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
|
{
|
|
NetworkLog.LogInfo($"[Client-{OwnerClientId}] Connection approved! Synchronizing...");
|
|
}
|
|
networkManager.LocalClientId = OwnerClientId;
|
|
networkManager.MessageManager.SetLocalClientId(networkManager.LocalClientId);
|
|
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
|
|
|
|
if (networkManager.DistributedAuthorityMode)
|
|
{
|
|
networkManager.SetSessionOwner(CurrentSessionOwner);
|
|
if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement)
|
|
{
|
|
networkManager.SceneManager.InitializeScenesLoaded();
|
|
}
|
|
}
|
|
|
|
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick);
|
|
networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport.
|
|
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
|
|
|
|
networkManager.ConnectionManager.LocalClient.SetRole(false, true, networkManager);
|
|
networkManager.ConnectionManager.LocalClient.IsApproved = true;
|
|
networkManager.ConnectionManager.LocalClient.ClientId = OwnerClientId;
|
|
// Stop the client-side approval timeout coroutine since we are approved.
|
|
networkManager.ConnectionManager.StopClientApprovalCoroutine();
|
|
|
|
networkManager.ConnectionManager.ConnectedClientIds.Clear();
|
|
foreach (var clientId in ConnectedClientIds)
|
|
{
|
|
if (!networkManager.ConnectionManager.ConnectedClientIds.Contains(clientId))
|
|
{
|
|
networkManager.ConnectionManager.AddClient(clientId);
|
|
}
|
|
}
|
|
|
|
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
|
|
if (!networkManager.NetworkConfig.EnableSceneManagement)
|
|
{
|
|
// DANGO-TODO: This is a temporary fix for no DA CMB scene event handling.
|
|
// We will either use this same concept or provide some way for the CMB state plugin to handle it.
|
|
if (networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner)
|
|
{
|
|
networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
|
}
|
|
else
|
|
{
|
|
networkManager.SpawnManager.DestroySceneObjects();
|
|
}
|
|
|
|
m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount);
|
|
|
|
// Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing
|
|
// to create a list to hold the data. This is a breach of convention for performance reasons.
|
|
for (ushort i = 0; i < sceneObjectCount; i++)
|
|
{
|
|
var sceneObject = new NetworkObject.SceneObject();
|
|
sceneObject.Deserialize(m_ReceivedSceneObjectData);
|
|
NetworkObject.AddSceneObject(sceneObject, m_ReceivedSceneObjectData, networkManager);
|
|
}
|
|
|
|
// Mark the client being connected
|
|
networkManager.IsConnectedClient = true;
|
|
|
|
if (networkManager.AutoSpawnPlayerPrefabClientSide)
|
|
{
|
|
networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId);
|
|
}
|
|
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
|
{
|
|
NetworkLog.LogInfo($"[Client-{OwnerClientId}][Scene Management Disabled] Synchronization complete!");
|
|
}
|
|
// When scene management is disabled we notify after everything is synchronized
|
|
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
|
|
|
|
// For convenience, notify all NetworkBehaviours that synchronization is complete.
|
|
networkManager.SpawnManager.NotifyNetworkObjectsSynchronized();
|
|
}
|
|
else
|
|
{
|
|
if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement)
|
|
{
|
|
// Mark the client being connected
|
|
networkManager.IsConnectedClient = true;
|
|
|
|
networkManager.SceneManager.IsRestoringSession = IsRestoredSession;
|
|
|
|
if (!IsRestoredSession)
|
|
{
|
|
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
|
|
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
|
|
|
|
// Spawn any in-scene placed NetworkObjects
|
|
networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
|
|
|
// Spawn the local player of the session owner
|
|
if (networkManager.AutoSpawnPlayerPrefabClientSide)
|
|
{
|
|
networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId);
|
|
}
|
|
|
|
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
|
|
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
|
|
|
|
// With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself,
|
|
// we need to notify the session owner that everything should be synchronized/spawned at this time.
|
|
networkManager.SpawnManager.NotifyNetworkObjectsSynchronized();
|
|
|
|
// When scene management is enabled and since the session owner is synchronizing the service (i.e. acting like host),
|
|
// we need to locallyh invoke the OnClientConnected callback at this point in time.
|
|
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(OwnerClientId);
|
|
}
|
|
}
|
|
}
|
|
ConnectedClientIds.Dispose();
|
|
}
|
|
}
|
|
}
|