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.8.0] - 2023-12-12 ### Added - Added a new RPC attribute, which is simply `Rpc`. (#2762) - This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs. - This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately. - This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option. - Added `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762) - Added `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762) - Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735) - Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735) - Added `NetworkObject.InstantiateAndSpawn` and `NetworkSpawnManager.InstantiateAndSpawn` methods to simplify prefab spawning by assuring that the prefab is valid and applies any override prior to instantiating the `GameObject` and spawning the `NetworkObject` instance. (#2710) ### Fixed - Fixed issue where a client disconnected by a server-host would not receive a local notification. (#2789) - Fixed issue where a server-host could shutdown during a relay connection but periodically the transport disconnect message sent to any connected clients could be dropped. (#2789) - Fixed issue where a host could disconnect its local client but remain running as a server. (#2789) - Fixed issue where `OnClientDisconnectedCallback` was not being invoked under certain conditions. (#2789) - Fixed issue where `OnClientDisconnectedCallback` was always returning 0 as the client identifier. (#2789) - Fixed issue where if a host or server shutdown while a client owned NetworkObjects (other than the player) it would throw an exception. (#2789) - Fixed issue where setting values on a `NetworkVariable` or `NetworkList` within `OnNetworkDespawn` during a shutdown sequence would throw an exception. (#2789) - Fixed issue where a teleport state could potentially be overridden by a previous unreliable delta state. (#2777) - Fixed issue where `NetworkTransform` was using the `NetworkManager.ServerTime.Tick` as opposed to `NetworkManager.NetworkTickSystem.ServerTime.Tick` during the authoritative side's tick update where it performed a delta state check. (#2777) - Fixed issue where a parented in-scene placed NetworkObject would be destroyed upon a client or server exiting a network session but not unloading the original scene in which the NetworkObject was placed. (#2737) - Fixed issue where during client synchronization and scene loading, when client synchronization or the scene loading mode are set to `LoadSceneMode.Single`, a `CreateObjectMessage` could be received, processed, and the resultant spawned `NetworkObject` could be instantiated in the client's currently active scene that could, towards the end of the client synchronization or loading process, be unloaded and cause the newly created `NetworkObject` to be destroyed (and throw and exception). (#2735) - Fixed issue where a `NetworkTransform` instance with interpolation enabled would result in wide visual motion gaps (stuttering) under above normal latency conditions and a 1-5% or higher packet are drop rate. (#2713) - Fixed issue where you could not have multiple source network prefab overrides targeting the same network prefab as their override. (#2710) ### Changed - Changed the server or host shutdown so it will now perform a "soft shutdown" when `NetworkManager.Shutdown` is invoked. This will send a disconnect notification to all connected clients and the server-host will wait for all connected clients to disconnect or timeout after a 5 second period before completing the shutdown process. (#2789) - Changed `OnClientDisconnectedCallback` will now return the assigned client identifier on the local client side if the client was approved and assigned one prior to being disconnected. (#2789) - Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777) - `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762) - Changed `NetworkSceneManager` to return a `SceneEventProgress` status and not throw exceptions for methods invoked when scene management is disabled and when a client attempts to access a `NetworkSceneManager` method by a client. (#2735) - Changed `NetworkTransform` authoritative instance tick registration so a single `NetworkTransform` specific tick event update will update all authoritative instances to improve perofmance. (#2713) - Changed `NetworkPrefabs.OverrideToNetworkPrefab` dictionary is no longer used/populated due to it ending up being related to a regression bug and not allowing more than one override to be assigned to a network prefab asset. (#2710) - Changed in-scene placed `NetworkObject`s now store their source network prefab asset's `GlobalObjectIdHash` internally that is used, when scene management is disabled, by clients to spawn the correct prefab even if the `NetworkPrefab` entry has an override. This does not impact dynamically spawning the same prefab which will yield the override on both host and client. (#2710) - Changed in-scene placed `NetworkObject`s no longer require a `NetworkPrefab` entry with `GlobalObjectIdHash` override in order for clients to properly synchronize. (#2710) - Changed in-scene placed `NetworkObject`s now set their `IsSceneObject` value when generating their `GlobalObjectIdHash` value. (#2710) - Changed the default `NetworkConfig.SpawnTimeout` value from 1.0s to 10.0s. (#2710)
309 lines
12 KiB
C#
309 lines
12 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using AsyncOperation = UnityEngine.AsyncOperation;
|
|
|
|
namespace Unity.Netcode
|
|
{
|
|
/// <summary>
|
|
/// 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.<br/>
|
|
/// <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
|
|
/// via the <see cref="SceneEvent"/> parameter.</em>
|
|
/// </summary>
|
|
public enum SceneEventProgressStatus
|
|
{
|
|
/// <summary>
|
|
/// No scene event progress status can be used to initialize a variable that will be checked over time.
|
|
/// </summary>
|
|
None,
|
|
/// <summary>
|
|
/// The scene event was successfully started.
|
|
/// </summary>
|
|
Started,
|
|
/// <summary>
|
|
/// Returned if you try to unload a scene that was not yet loaded.
|
|
/// </summary>
|
|
SceneNotLoaded,
|
|
/// <summary>
|
|
/// Returned if you try to start a new scene event before a previous one is finished.
|
|
/// </summary>
|
|
SceneEventInProgress,
|
|
/// <summary>
|
|
/// Returned if the scene name used with <see cref="NetworkSceneManager.LoadScene(string, LoadSceneMode)"/>
|
|
/// or <see cref="NetworkSceneManager.UnloadScene(Scene)"/>is invalid.
|
|
/// </summary>
|
|
InvalidSceneName,
|
|
/// <summary>
|
|
/// Server side: Returned if the <see cref="NetworkSceneManager.VerifySceneBeforeLoading"/> delegate handler returns false
|
|
/// (<em>i.e. scene is considered not valid/safe to load</em>).
|
|
/// </summary>
|
|
SceneFailedVerification,
|
|
/// <summary>
|
|
/// This is used for internal error notifications.<br/>
|
|
/// 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/>
|
|
/// </summary>
|
|
InternalNetcodeError,
|
|
/// <summary>
|
|
/// This is returned when an unload or load action is attempted and scene management is disabled
|
|
/// </summary>
|
|
SceneManagementNotEnabled,
|
|
/// <summary>
|
|
/// This is returned when a client attempts to perform a server only action
|
|
/// </summary>
|
|
ServerOnlyAction,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server side only:
|
|
/// This tracks the progress of clients during a load or unload scene event
|
|
/// </summary>
|
|
internal class SceneEventProgress
|
|
{
|
|
/// <summary>
|
|
/// List of clientIds of those clients that is done loading the scene.
|
|
/// </summary>
|
|
internal Dictionary<ulong, bool> ClientsProcessingSceneEvent { get; } = new Dictionary<ulong, bool>();
|
|
internal List<ulong> ClientsThatDisconnected = new List<ulong>();
|
|
|
|
/// <summary>
|
|
/// This is when the current scene event will have timed out
|
|
/// </summary>
|
|
internal float WhenSceneEventHasTimedOut;
|
|
|
|
/// <summary>
|
|
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
|
/// </summary>
|
|
internal delegate bool OnCompletedDelegate(SceneEventProgress sceneEventProgress);
|
|
|
|
/// <summary>
|
|
/// The callback invoked when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
|
/// </summary>
|
|
internal OnCompletedDelegate OnComplete;
|
|
|
|
internal Action<uint> OnSceneEventCompleted;
|
|
|
|
/// <summary>
|
|
/// This will make sure that we only have timed out if we never completed
|
|
/// </summary>
|
|
internal bool HasTimedOut()
|
|
{
|
|
return WhenSceneEventHasTimedOut <= m_NetworkManager.RealTimeProvider.RealTimeSinceStartup;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The hash value generated from the full scene path
|
|
/// </summary>
|
|
internal uint SceneHash { get; set; }
|
|
|
|
internal Guid Guid { get; } = Guid.NewGuid();
|
|
internal uint SceneEventId;
|
|
|
|
private Coroutine m_TimeOutCoroutine;
|
|
private AsyncOperation m_AsyncOperation;
|
|
|
|
private NetworkManager m_NetworkManager { get; }
|
|
|
|
internal SceneEventProgressStatus Status { get; set; }
|
|
|
|
internal SceneEventType SceneEventType { get; set; }
|
|
|
|
internal LoadSceneMode LoadSceneMode;
|
|
|
|
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
|
|
{
|
|
var clients = new List<ulong>();
|
|
if (completedSceneEvent)
|
|
{
|
|
// If we are the host, then add the host-client to the list
|
|
// of clients that completed if the AsyncOperation is done.
|
|
if (m_NetworkManager.IsHost && m_AsyncOperation.isDone)
|
|
{
|
|
clients.Add(m_NetworkManager.LocalClientId);
|
|
}
|
|
|
|
// Add all clients that completed the scene event
|
|
foreach (var clientStatus in ClientsProcessingSceneEvent)
|
|
{
|
|
if (clientStatus.Value == completedSceneEvent)
|
|
{
|
|
clients.Add(clientStatus.Key);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are the host, then add the host-client to the list
|
|
// of clients that did not complete if the AsyncOperation is
|
|
// not done.
|
|
if (m_NetworkManager.IsHost && !m_AsyncOperation.isDone)
|
|
{
|
|
clients.Add(m_NetworkManager.LocalClientId);
|
|
}
|
|
|
|
// If we are getting the list of clients that have not completed the
|
|
// scene event, then add any clients that disconnected during this
|
|
// scene event.
|
|
clients.AddRange(ClientsThatDisconnected);
|
|
}
|
|
return clients;
|
|
}
|
|
|
|
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
|
|
{
|
|
if (status == SceneEventProgressStatus.Started)
|
|
{
|
|
m_NetworkManager = networkManager;
|
|
|
|
if (networkManager.IsServer)
|
|
{
|
|
m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
|
|
// Track the clients that were connected when we started this event
|
|
foreach (var connectedClientId in networkManager.ConnectedClientsIds)
|
|
{
|
|
// Ignore the host client
|
|
if (NetworkManager.ServerClientId == connectedClientId)
|
|
{
|
|
continue;
|
|
}
|
|
ClientsProcessingSceneEvent.Add(connectedClientId, false);
|
|
}
|
|
|
|
WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
|
|
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
|
}
|
|
}
|
|
Status = status;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove the client from the clients processing the current scene event
|
|
/// Add this client to the clients that disconnected list
|
|
/// </summary>
|
|
private void OnClientDisconnectCallback(ulong clientId)
|
|
{
|
|
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
|
|
{
|
|
ClientsThatDisconnected.Add(clientId);
|
|
ClientsProcessingSceneEvent.Remove(clientId);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine that checks to see if the scene event is complete every network tick period.
|
|
/// This will handle completing the scene event when one or more client(s) disconnect(s)
|
|
/// during a scene event and if it does not complete within the scene loading time out period
|
|
/// it will time out the scene event.
|
|
/// </summary>
|
|
internal IEnumerator TimeOutSceneEventProgress()
|
|
{
|
|
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
|
|
while (!HasTimedOut())
|
|
{
|
|
yield return waitForNetworkTick;
|
|
|
|
TryFinishingSceneEventProgress();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the client's scene event progress to finished/true
|
|
/// </summary>
|
|
internal void ClientFinishedSceneEvent(ulong clientId)
|
|
{
|
|
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
|
|
{
|
|
ClientsProcessingSceneEvent[clientId] = true;
|
|
TryFinishingSceneEventProgress();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the scene event has finished for both
|
|
/// client(s) and server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The server checks if all known clients processing this scene event
|
|
/// have finished and then it returns its local AsyncOperation status.
|
|
/// Clients finish when their AsyncOperation finishes.
|
|
/// </remarks>
|
|
private bool HasFinished()
|
|
{
|
|
// If the network session is terminated/terminating then finish tracking
|
|
// this scene event
|
|
if (!IsNetworkSessionActive())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Clients skip over this
|
|
foreach (var clientStatus in ClientsProcessingSceneEvent)
|
|
{
|
|
if (!clientStatus.Value)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Return the local scene event's AsyncOperation status
|
|
// Note: Integration tests process scene loading through a queue
|
|
// and the AsyncOperation could not be assigned for several
|
|
// network tick periods. Return false if that is the case.
|
|
return m_AsyncOperation == null ? false : m_AsyncOperation.isDone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the AsyncOperation for the scene load/unload event
|
|
/// </summary>
|
|
internal void SetAsyncOperation(AsyncOperation asyncOperation)
|
|
{
|
|
m_AsyncOperation = asyncOperation;
|
|
m_AsyncOperation.completed += new Action<AsyncOperation>(asyncOp2 =>
|
|
{
|
|
// Don't invoke the callback if the network session is disconnected
|
|
// during a SceneEventProgress
|
|
if (IsNetworkSessionActive())
|
|
{
|
|
OnSceneEventCompleted?.Invoke(SceneEventId);
|
|
}
|
|
|
|
// Go ahead and try finishing even if the network session is terminated/terminating
|
|
// as we might need to stop the coroutine
|
|
TryFinishingSceneEventProgress();
|
|
});
|
|
}
|
|
|
|
|
|
internal bool IsNetworkSessionActive()
|
|
{
|
|
return m_NetworkManager != null && m_NetworkManager.IsListening && !m_NetworkManager.ShutdownInProgress;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Will try to finish the current scene event in progress as long as
|
|
/// all conditions are met.
|
|
/// </summary>
|
|
internal void TryFinishingSceneEventProgress()
|
|
{
|
|
if (HasFinished() || HasTimedOut())
|
|
{
|
|
// Don't attempt to finalize this scene event if we are no longer listening or a shutdown is in progress
|
|
if (IsNetworkSessionActive())
|
|
{
|
|
OnComplete?.Invoke(this);
|
|
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
|
m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
|
|
}
|
|
|
|
if (m_TimeOutCoroutine != null)
|
|
{
|
|
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|