com.unity.netcode.gameobjects@1.0.0-pre.10
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.10] - 2022-06-21 ### Added - Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994) - Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994) - Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969) - Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950) ### Changed - Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025) - (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972) ### Removed ### Fixed - Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017) - Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009) - Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003) - Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985) - Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984) - Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975) - Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973) - Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972) - Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961) - Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976) - Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947) - Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946) - Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946) - Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946) - Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946) - Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946) - Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946) - Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
This commit is contained in:
@@ -60,6 +60,8 @@ namespace Unity.Netcode
|
||||
|
||||
private NetworkPrefabHandler m_PrefabHandler;
|
||||
|
||||
internal Dictionary<ulong, ConnectionApprovalResponse> ClientsToApprove = new Dictionary<ulong, ConnectionApprovalResponse>();
|
||||
|
||||
public NetworkPrefabHandler PrefabHandler
|
||||
{
|
||||
get
|
||||
@@ -206,12 +208,14 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets or sets if the application should be set to run in background
|
||||
/// </summary>
|
||||
[HideInInspector] public bool RunInBackground = true;
|
||||
[HideInInspector]
|
||||
public bool RunInBackground = true;
|
||||
|
||||
/// <summary>
|
||||
/// The log level to use
|
||||
/// </summary>
|
||||
[HideInInspector] public LogLevel LogLevel = LogLevel.Normal;
|
||||
[HideInInspector]
|
||||
public LogLevel LogLevel = LogLevel.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// The singleton instance of the NetworkManager
|
||||
@@ -358,26 +362,71 @@ namespace Unity.Netcode
|
||||
public event Action OnServerStarted = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type called when connection has been approved. This only has to be set on the server.
|
||||
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
|
||||
/// </summary>
|
||||
/// <param name="createPlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
|
||||
/// <param name="playerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
|
||||
/// <param name="approved">Whether or not the client was approved</param>
|
||||
/// <param name="position">The position to spawn the client at. If null, the prefab position is used.</param>
|
||||
/// <param name="rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
|
||||
public delegate void ConnectionApprovedDelegate(bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation);
|
||||
/// <remarks>
|
||||
/// A failure of the transport is always followed by the <see cref="NetworkManager"/> shutting down. Recovering
|
||||
/// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
|
||||
/// recreating a new service allocation depending on the transport) and restarting the client/server/host.
|
||||
/// </remarks>
|
||||
public event Action OnTransportFailure = null;
|
||||
|
||||
/// <summary>
|
||||
/// The callback to invoke during connection approval
|
||||
/// Connection Approval Response
|
||||
/// </summary>
|
||||
public event Action<byte[], ulong, ConnectionApprovedDelegate> ConnectionApprovalCallback = null;
|
||||
/// <param name="Approved">Whether or not the client was approved</param>
|
||||
/// <param name="CreatePlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
|
||||
/// <param name="PlayerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
|
||||
/// <param name="Position">The position to spawn the client at. If null, the prefab position is used.</param>
|
||||
/// <param name="Rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
|
||||
/// <param name="Pending">If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.</param>
|
||||
public class ConnectionApprovalResponse
|
||||
{
|
||||
public bool Approved;
|
||||
public bool CreatePlayerObject;
|
||||
public uint? PlayerPrefabHash;
|
||||
public Vector3? Position;
|
||||
public Quaternion? Rotation;
|
||||
public bool Pending;
|
||||
}
|
||||
|
||||
internal void InvokeConnectionApproval(byte[] payload, ulong clientId, ConnectionApprovedDelegate action) => ConnectionApprovalCallback?.Invoke(payload, clientId, action);
|
||||
/// <summary>
|
||||
/// Connection Approval Request
|
||||
/// </summary>
|
||||
/// <param name="Payload">The connection data payload</param>
|
||||
/// <param name="ClientNetworkId">The Network Id of the client we are about to handle</param>
|
||||
public struct ConnectionApprovalRequest
|
||||
{
|
||||
public byte[] Payload;
|
||||
public ulong ClientNetworkId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
|
||||
/// </summary>
|
||||
public Action<ConnectionApprovalRequest, ConnectionApprovalResponse> ConnectionApprovalCallback
|
||||
{
|
||||
get => m_ConnectionApprovalCallback;
|
||||
set
|
||||
{
|
||||
if (value != null && value.GetInvocationList().Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException($"Only one {nameof(ConnectionApprovalCallback)} can be registered at a time.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ConnectionApprovalCallback = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Action<ConnectionApprovalRequest, ConnectionApprovalResponse> m_ConnectionApprovalCallback;
|
||||
|
||||
/// <summary>
|
||||
/// The current NetworkConfig
|
||||
/// </summary>
|
||||
[HideInInspector] public NetworkConfig NetworkConfig;
|
||||
[HideInInspector]
|
||||
public NetworkConfig NetworkConfig;
|
||||
|
||||
/// <summary>
|
||||
/// The current host name we are connected to, used to validate certificate
|
||||
@@ -389,7 +438,7 @@ namespace Unity.Netcode
|
||||
internal static event Action OnSingletonReady;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
internal void OnValidate()
|
||||
{
|
||||
if (NetworkConfig == null)
|
||||
{
|
||||
@@ -541,6 +590,47 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a prefab from the prefab list.
|
||||
/// As with AddNetworkPrefab, this is specific to the client it's called on -
|
||||
/// calling it on the server does not automatically remove anything on any of the
|
||||
/// client processes.
|
||||
///
|
||||
/// Like AddNetworkPrefab, when NetworkConfig.ForceSamePrefabs is enabled,
|
||||
/// this cannot be called after connecting.
|
||||
/// </summary>
|
||||
/// <param name="prefab"></param>
|
||||
public void RemoveNetworkPrefab(GameObject prefab)
|
||||
{
|
||||
if (IsListening && NetworkConfig.ForceSamePrefabs)
|
||||
{
|
||||
throw new Exception($"Prefabs cannot be removed after starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
|
||||
}
|
||||
|
||||
var globalObjectIdHash = prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||
for (var i = 0; i < NetworkConfig.NetworkPrefabs.Count; ++i)
|
||||
{
|
||||
if (NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash == globalObjectIdHash)
|
||||
{
|
||||
NetworkConfig.NetworkPrefabs.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||
{
|
||||
PrefabHandler.RemoveHandler(globalObjectIdHash);
|
||||
}
|
||||
if (NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, out var targetPrefab))
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Remove(globalObjectIdHash);
|
||||
var targetHash = targetPrefab.Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||
if (NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetHash))
|
||||
{
|
||||
NetworkConfig.OverrideToNetworkPrefab.Remove(targetHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAddPrefab(NetworkPrefab networkPrefab, out uint sourcePrefabGlobalObjectIdHash, out uint targetPrefabGlobalObjectIdHash, int index = -1)
|
||||
{
|
||||
sourcePrefabGlobalObjectIdHash = 0;
|
||||
@@ -865,6 +955,7 @@ namespace Unity.Netcode
|
||||
m_ConnectedClientIds.Clear();
|
||||
LocalClient = null;
|
||||
NetworkObject.OrphanChildren.Clear();
|
||||
ClientsToApprove.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -899,6 +990,7 @@ namespace Unity.Netcode
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
@@ -926,6 +1018,7 @@ namespace Unity.Netcode
|
||||
if (!NetworkConfig.NetworkTransport.StartClient())
|
||||
{
|
||||
Debug.LogError($"Client is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
@@ -958,6 +1051,7 @@ namespace Unity.Netcode
|
||||
if (!NetworkConfig.NetworkTransport.StartServer())
|
||||
{
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
@@ -970,27 +1064,29 @@ namespace Unity.Netcode
|
||||
IsClient = true;
|
||||
IsListening = true;
|
||||
|
||||
if (NetworkConfig.ConnectionApproval)
|
||||
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
|
||||
{
|
||||
InvokeConnectionApproval(NetworkConfig.ConnectionData, ServerClientId,
|
||||
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
|
||||
var response = new ConnectionApprovalResponse();
|
||||
ConnectionApprovalCallback(new ConnectionApprovalRequest { Payload = NetworkConfig.ConnectionData, ClientNetworkId = ServerClientId }, response);
|
||||
if (!response.Approved)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
// You cannot decline the local server. Force approved to true
|
||||
if (!approved)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
"You cannot decline the host connection. The connection was automatically approved.");
|
||||
}
|
||||
}
|
||||
NetworkLog.LogWarning("You cannot decline the host connection. The connection was automatically approved.");
|
||||
}
|
||||
}
|
||||
|
||||
HandleApproval(ServerClientId, createPlayerObject, playerPrefabHash, true, position, rotation);
|
||||
});
|
||||
response.Approved = true;
|
||||
HandleConnectionApproval(ServerClientId, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleApproval(ServerClientId, NetworkConfig.PlayerPrefab != null, null, true, null, null);
|
||||
var response = new ConnectionApprovalResponse
|
||||
{
|
||||
Approved = true,
|
||||
CreatePlayerObject = NetworkConfig.PlayerPrefab != null
|
||||
};
|
||||
HandleConnectionApproval(ServerClientId, response);
|
||||
}
|
||||
|
||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
@@ -1076,7 +1172,6 @@ namespace Unity.Netcode
|
||||
private void Awake()
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||
NetworkVariableHelper.InitializeAllBaseDelegates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1327,6 +1422,43 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPendingApprovals()
|
||||
{
|
||||
List<ulong> senders = null;
|
||||
|
||||
foreach (var responsePair in ClientsToApprove)
|
||||
{
|
||||
var response = responsePair.Value;
|
||||
var senderId = responsePair.Key;
|
||||
|
||||
if (!response.Pending)
|
||||
{
|
||||
try
|
||||
{
|
||||
HandleConnectionApproval(senderId, response);
|
||||
|
||||
if (senders == null)
|
||||
{
|
||||
senders = new List<ulong>();
|
||||
}
|
||||
senders.Add(senderId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (senders != null)
|
||||
{
|
||||
foreach (var sender in senders)
|
||||
{
|
||||
ClientsToApprove.Remove(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkEarlyUpdate()
|
||||
{
|
||||
if (!IsListening)
|
||||
@@ -1334,6 +1466,8 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessPendingApprovals();
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_TransportPoll.Begin();
|
||||
#endif
|
||||
@@ -1554,6 +1688,12 @@ namespace Unity.Netcode
|
||||
s_TransportDisconnect.End();
|
||||
#endif
|
||||
break;
|
||||
|
||||
case NetworkEvent.TransportFailure:
|
||||
Debug.LogError($"Shutting down due to network transport failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1818,15 +1958,11 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Server Side: Handles the approval of a client
|
||||
/// </summary>
|
||||
/// <param name="ownerClientId">client being approved</param>
|
||||
/// <param name="createPlayerObject">whether we want to create a player or not</param>
|
||||
/// <param name="playerPrefabHash">the GlobalObjectIdHash value for the Network Prefab to create as the player</param>
|
||||
/// <param name="approved">Is the player approved or not?</param>
|
||||
/// <param name="position">Used when createPlayerObject is true, position of the player when spawned </param>
|
||||
/// <param name="rotation">Used when createPlayerObject is true, rotation of the player when spawned</param>
|
||||
internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation)
|
||||
/// <param name="ownerClientId">The Network Id of the client being approved</param>
|
||||
/// <param name="response">The response to allow the player in or not, with its parameters</param>
|
||||
internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalResponse response)
|
||||
{
|
||||
if (approved)
|
||||
if (response.Approved)
|
||||
{
|
||||
// Inform new client it got approved
|
||||
PendingClients.Remove(ownerClientId);
|
||||
@@ -1836,10 +1972,23 @@ namespace Unity.Netcode
|
||||
m_ConnectedClientsList.Add(client);
|
||||
m_ConnectedClientIds.Add(client.ClientId);
|
||||
|
||||
if (createPlayerObject)
|
||||
if (response.CreatePlayerObject)
|
||||
{
|
||||
var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, null, position, rotation);
|
||||
SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, false);
|
||||
var networkObject = SpawnManager.CreateLocalNetworkObject(
|
||||
isSceneObject: false,
|
||||
response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash,
|
||||
ownerClientId,
|
||||
parentNetworkId: null,
|
||||
networkSceneHandle: null,
|
||||
response.Position,
|
||||
response.Rotation);
|
||||
SpawnManager.SpawnNetworkObjectLocally(
|
||||
networkObject,
|
||||
SpawnManager.GetNetworkObjectId(),
|
||||
sceneObject: false,
|
||||
playerObject: true,
|
||||
ownerClientId,
|
||||
destroyWithScene: false);
|
||||
|
||||
ConnectedClients[ownerClientId].PlayerObject = networkObject;
|
||||
}
|
||||
@@ -1879,13 +2028,13 @@ namespace Unity.Netcode
|
||||
InvokeOnClientConnectedCallback(ownerClientId);
|
||||
}
|
||||
|
||||
if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
|
||||
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Separating this into a contained function call for potential further future separation of when this notification is sent.
|
||||
ApprovedPlayerSpawn(ownerClientId, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
|
||||
ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -75,7 +76,7 @@ namespace Unity.Netcode
|
||||
public bool IsPlayerObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// Gets if the object is the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkManager != null && IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId;
|
||||
|
||||
@@ -151,6 +152,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
private readonly HashSet<ulong> m_EmptyULongHashSet = new HashSet<ulong>();
|
||||
/// <summary>
|
||||
/// Returns Observers enumerator
|
||||
/// </summary>
|
||||
@@ -159,7 +161,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
return m_EmptyULongHashSet.GetEnumerator();
|
||||
}
|
||||
|
||||
return Observers.GetEnumerator();
|
||||
@@ -174,15 +176,62 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
return false;
|
||||
}
|
||||
return Observers.Contains(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the event the scene of origin gets unloaded, we keep
|
||||
/// the most important part to uniquely identify in-scene
|
||||
/// placed NetworkObjects
|
||||
/// </summary>
|
||||
internal int SceneOriginHandle = 0;
|
||||
|
||||
private Scene m_SceneOrigin;
|
||||
/// <summary>
|
||||
/// The scene where the NetworkObject was first instantiated
|
||||
/// Note: Primarily for in-scene placed NetworkObjects
|
||||
/// We need to keep track of the original scene of origin for
|
||||
/// the NetworkObject in order to be able to uniquely identify it
|
||||
/// using the scene of origin's handle.
|
||||
/// </summary>
|
||||
internal Scene SceneOrigin
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_SceneOrigin;
|
||||
}
|
||||
|
||||
return Observers.Contains(clientId);
|
||||
set
|
||||
{
|
||||
// The scene origin should only be set once.
|
||||
// Once set, it should never change.
|
||||
if (SceneOriginHandle == 0 && value.IsValid() && value.isLoaded)
|
||||
{
|
||||
m_SceneOrigin = value;
|
||||
SceneOriginHandle = value.handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to return the correct scene handle
|
||||
/// Note: Do not use this within NetworkSpawnManager.SpawnNetworkObjectLocallyCommon
|
||||
/// </summary>
|
||||
internal int GetSceneOriginHandle()
|
||||
{
|
||||
if (SceneOriginHandle == 0 && IsSpawned && IsSceneObject != false)
|
||||
{
|
||||
throw new Exception($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
|
||||
}
|
||||
return SceneOriginHandle != 0 ? SceneOriginHandle : gameObject.scene.handle;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
SetCachedParent(transform.parent);
|
||||
SceneOrigin = gameObject.scene;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -285,7 +334,8 @@ namespace Unity.Netcode
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
DestroyGameObject = true
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
@@ -372,7 +422,7 @@ namespace Unity.Netcode
|
||||
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
|
||||
}
|
||||
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene);
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene);
|
||||
|
||||
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
@@ -865,16 +915,17 @@ namespace Unity.Netcode
|
||||
public NetworkObject OwnerObject;
|
||||
public ulong TargetClientId;
|
||||
|
||||
public int NetworkSceneHandle;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(
|
||||
sizeof(HeaderData) +
|
||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
||||
(Header.IsReparented
|
||||
? FastBufferWriter.GetWriteSize(IsLatestParentSet) +
|
||||
(IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0)
|
||||
: 0)))
|
||||
var writeSize = sizeof(HeaderData);
|
||||
writeSize += Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||
writeSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
if (!writer.TryBeginWrite(writeSize))
|
||||
{
|
||||
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
@@ -900,6 +951,16 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// This is only set on in-scene placed NetworkObjects to reduce the over-all packet
|
||||
// sizes for dynamically spawned NetworkObjects.
|
||||
if (Header.IsSceneObject)
|
||||
{
|
||||
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
|
||||
}
|
||||
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
}
|
||||
|
||||
@@ -910,10 +971,12 @@ namespace Unity.Netcode
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
reader.ReadValue(out Header);
|
||||
if (!reader.TryBeginRead(
|
||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
||||
(Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) : 0)))
|
||||
var readSize = Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||
readSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
@@ -937,6 +1000,16 @@ namespace Unity.Netcode
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// Client-side NetworkSceneManagers use this to locate their local instance of the
|
||||
// NetworkObject instance.
|
||||
if (Header.IsSceneObject)
|
||||
{
|
||||
reader.ReadValueSafe(out NetworkSceneHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1006,6 +1079,7 @@ namespace Unity.Netcode
|
||||
Vector3? position = null;
|
||||
Quaternion? rotation = null;
|
||||
ulong? parentNetworkId = null;
|
||||
int? networkSceneHandle = null;
|
||||
|
||||
if (sceneObject.Header.HasTransform)
|
||||
{
|
||||
@@ -1018,10 +1092,15 @@ namespace Unity.Netcode
|
||||
parentNetworkId = sceneObject.ParentObjectId;
|
||||
}
|
||||
|
||||
if (sceneObject.Header.IsSceneObject)
|
||||
{
|
||||
networkSceneHandle = sceneObject.NetworkSceneHandle;
|
||||
}
|
||||
|
||||
//Attempt to create a local NetworkObject
|
||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(
|
||||
sceneObject.Header.IsSceneObject, sceneObject.Header.Hash,
|
||||
sceneObject.Header.OwnerClientId, parentNetworkId, position, rotation, sceneObject.Header.IsReparented);
|
||||
sceneObject.Header.OwnerClientId, parentNetworkId, networkSceneHandle, position, rotation, sceneObject.Header.IsReparented);
|
||||
|
||||
networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent);
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace Unity.Netcode
|
||||
/// Sends the named message
|
||||
/// </summary>
|
||||
/// <param name="messageName">The message name to send</param>
|
||||
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
|
||||
/// <param name="clientIds">The clients to send to</param>
|
||||
/// <param name="messageStream">The message stream containing the data</param>
|
||||
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
||||
public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
||||
|
||||
@@ -101,16 +101,24 @@ namespace Unity.Netcode
|
||||
{
|
||||
// Note: Delegate creation allocates.
|
||||
// Note: ToArray() also allocates. :(
|
||||
networkManager.InvokeConnectionApproval(ConnectionData, senderId,
|
||||
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
|
||||
var response = new NetworkManager.ConnectionApprovalResponse();
|
||||
networkManager.ClientsToApprove[senderId] = response;
|
||||
|
||||
networkManager.ConnectionApprovalCallback(
|
||||
new NetworkManager.ConnectionApprovalRequest
|
||||
{
|
||||
var localCreatePlayerObject = createPlayerObject;
|
||||
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, position, rotation);
|
||||
});
|
||||
Payload = ConnectionData,
|
||||
ClientNetworkId = senderId
|
||||
}, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.HandleApproval(senderId, networkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null);
|
||||
var response = new NetworkManager.ConnectionApprovalResponse
|
||||
{
|
||||
Approved = true,
|
||||
CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null
|
||||
};
|
||||
networkManager.HandleConnectionApproval(senderId, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
ObjectInfo.Deserialize(reader);
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo.Header.IsSceneObject, ObjectInfo.Header.Hash))
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Unity.Netcode
|
||||
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public bool DestroyGameObject;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Unity.Netcode
|
||||
/// Event based NetworkVariable container for syncing Lists
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the list</typeparam>
|
||||
public class NetworkList<T> : NetworkVariableSerialization<T> where T : unmanaged, IEquatable<T>
|
||||
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<T> m_ListAtLastReset = new NativeList<T>(64, Allocator.Persistent);
|
||||
@@ -72,34 +72,35 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe((ushort)m_DirtyEvents.Length);
|
||||
for (int i = 0; i < m_DirtyEvents.Length; i++)
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Type);
|
||||
switch (m_DirtyEvents[i].Type)
|
||||
var element = m_DirtyEvents.ElementAt(i);
|
||||
writer.WriteValueSafe(element.Type);
|
||||
switch (element.Type)
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.RemoveAt:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Clear:
|
||||
@@ -117,7 +118,7 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
|
||||
for (int i = 0; i < m_ListAtLastReset.Length; i++)
|
||||
{
|
||||
Write(writer, m_ListAtLastReset[i]);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out ushort count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
@@ -144,7 +145,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
m_List.Add(value);
|
||||
|
||||
if (OnListChanged != null)
|
||||
@@ -171,7 +172,7 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = value;
|
||||
|
||||
@@ -198,7 +199,7 @@ namespace Unity.Netcode
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
int index = m_List.IndexOf(value);
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -258,7 +259,7 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Unity.Netcode
|
||||
/// A variable that can be synchronized over the network.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariable<T> : NetworkVariableSerialization<T> where T : unmanaged
|
||||
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate type for value changed event
|
||||
@@ -102,7 +102,7 @@ namespace Unity.Netcode
|
||||
// would be stored in different fields
|
||||
|
||||
T previousValue = m_InternalValue;
|
||||
Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
@@ -115,13 +115,13 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void ReadField(FastBufferReader reader)
|
||||
{
|
||||
Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
Write(writer, m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
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 InitializeDelegatesNetworkSerializable<T>() where T : unmanaged, INetworkSerializable
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteNetworkSerializable);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadNetworkSerializable);
|
||||
}
|
||||
internal static void InitializeDelegatesStruct<T>() where T : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteStruct);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadStruct);
|
||||
}
|
||||
internal static void InitializeDelegatesEnum<T>() where T : unmanaged, Enum
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteEnum);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadEnum);
|
||||
}
|
||||
internal static void InitializeDelegatesPrimitive<T>() where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WritePrimitive);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadPrimitive);
|
||||
}
|
||||
|
||||
internal static void InitializeAllBaseDelegates()
|
||||
{
|
||||
// Built-in C# types, serialized through a generic method
|
||||
InitializeDelegatesPrimitive<bool>();
|
||||
InitializeDelegatesPrimitive<byte>();
|
||||
InitializeDelegatesPrimitive<sbyte>();
|
||||
InitializeDelegatesPrimitive<char>();
|
||||
InitializeDelegatesPrimitive<decimal>();
|
||||
InitializeDelegatesPrimitive<float>();
|
||||
InitializeDelegatesPrimitive<double>();
|
||||
InitializeDelegatesPrimitive<short>();
|
||||
InitializeDelegatesPrimitive<ushort>();
|
||||
InitializeDelegatesPrimitive<int>();
|
||||
InitializeDelegatesPrimitive<uint>();
|
||||
InitializeDelegatesPrimitive<long>();
|
||||
InitializeDelegatesPrimitive<ulong>();
|
||||
|
||||
// Built-in Unity types, serialized with specific overloads because they're structs without ISerializeByMemcpy attached
|
||||
NetworkVariableSerialization<Vector2>.SetWriteDelegate((FastBufferWriter writer, in Vector2 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Vector3>.SetWriteDelegate((FastBufferWriter writer, in Vector3 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Vector4>.SetWriteDelegate((FastBufferWriter writer, in Vector4 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Quaternion>.SetWriteDelegate((FastBufferWriter writer, in Quaternion value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Color>.SetWriteDelegate((FastBufferWriter writer, in Color value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Color32>.SetWriteDelegate((FastBufferWriter writer, in Color32 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Ray>.SetWriteDelegate((FastBufferWriter writer, in Ray value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Ray2D>.SetWriteDelegate((FastBufferWriter writer, in Ray2D value) => { writer.WriteValueSafe(value); });
|
||||
|
||||
NetworkVariableSerialization<Vector2>.SetReadDelegate((FastBufferReader reader, out Vector2 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Vector3>.SetReadDelegate((FastBufferReader reader, out Vector3 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Vector4>.SetReadDelegate((FastBufferReader reader, out Vector4 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Quaternion>.SetReadDelegate((FastBufferReader reader, out Quaternion value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Color>.SetReadDelegate((FastBufferReader reader, out Color value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Color32>.SetReadDelegate((FastBufferReader reader, out Color32 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Ray>.SetReadDelegate((FastBufferReader reader, out Ray value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Ray2D>.SetReadDelegate((FastBufferReader reader, out Ray2D value) => { reader.ReadValueSafe(out value); });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e54b65208bd3bbe4eaf62ca0384ae21f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,169 +1,248 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface used by NetworkVariables to serialize them
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal interface INetworkVariableSerializer<T>
|
||||
{
|
||||
// Write has to be taken by ref here because of INetworkSerializable
|
||||
// Open Instance Delegates (pointers to methods without an instance attached to them)
|
||||
// require the first parameter passed to them (the instance) to be passed by ref.
|
||||
// So foo.Bar() becomes BarDelegate(ref foo);
|
||||
// Taking T as an in parameter like we do in other places would require making a copy
|
||||
// of it to pass it as a ref parameter.
|
||||
public void Write(FastBufferWriter writer, ref T value);
|
||||
public void Read(FastBufferReader reader, out T value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic serializer for unmanaged types.
|
||||
/// This covers primitives, built-in unity types, and IForceSerializeByMemcpy
|
||||
/// Since all of those ultimately end up calling WriteUnmanagedSafe, this simplifies things
|
||||
/// by calling that directly - thus preventing us from having to have a specific T that meets
|
||||
/// the specific constraints that the various generic WriteValue calls require.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class UnmanagedTypeSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
writer.WriteUnmanagedSafe(value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
reader.ReadUnmanagedSafe(out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for FixedStrings, which does the same thing FastBufferWriter/FastBufferReader do,
|
||||
/// but is implemented to get the data it needs using open instance delegates that are passed in
|
||||
/// via reflection. This prevents needing T to meet any interface requirements (which isn't achievable
|
||||
/// without incurring GC allocs on every call to Write or Read - reflection + Open Instance Delegates
|
||||
/// circumvent that.)
|
||||
///
|
||||
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||
/// aren't known to actually contain those methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
{
|
||||
internal delegate int GetLengthDelegate(ref T value);
|
||||
internal delegate void SetLengthDelegate(ref T value, int length);
|
||||
internal unsafe delegate byte* GetUnsafePtrDelegate(ref T value);
|
||||
|
||||
internal GetLengthDelegate GetLength;
|
||||
internal SetLengthDelegate SetLength;
|
||||
internal GetUnsafePtrDelegate GetUnsafePtr;
|
||||
|
||||
public unsafe void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
int length = GetLength(ref value);
|
||||
byte* data = GetUnsafePtr(ref value);
|
||||
writer.WriteUnmanagedSafe(length);
|
||||
writer.WriteBytesSafe(data, length);
|
||||
}
|
||||
public unsafe void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
value = new T();
|
||||
reader.ReadValueSafe(out int length);
|
||||
SetLength(ref value, length);
|
||||
reader.ReadBytesSafe(GetUnsafePtr(ref value), length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for INetworkSerializable types, which does the same thing
|
||||
/// FastBufferWriter/FastBufferReader do, but is implemented to call the NetworkSerialize() method
|
||||
/// via open instance delegates passed in via reflection. This prevents needing T to meet any interface
|
||||
/// requirements (which isn't achievable without incurring GC allocs on every call to Write or Read -
|
||||
/// reflection + Open Instance Delegates circumvent that.)
|
||||
///
|
||||
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||
/// aren't known to actually contain those methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class NetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
{
|
||||
internal delegate void WriteValueDelegate(ref T value, BufferSerializer<BufferSerializerWriter> serializer);
|
||||
internal delegate void ReadValueDelegate(ref T value, BufferSerializer<BufferSerializerReader> serializer);
|
||||
|
||||
internal WriteValueDelegate WriteValue;
|
||||
internal ReadValueDelegate ReadValue;
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||
WriteValue(ref value, bufferSerializer);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
value = new T();
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||
ReadValue(ref value, bufferSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used to register user serialization with NetworkVariables for types
|
||||
/// that are serialized via user serialization, such as with FastBufferReader and FastBufferWriter
|
||||
/// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows
|
||||
/// users to tell NetworkVariable about those extension methods (or simply pass in a lambda)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class UserNetworkVariableSerialization<T>
|
||||
{
|
||||
public delegate void WriteValueDelegate(FastBufferWriter writer, in T value);
|
||||
public delegate void ReadValueDelegate(FastBufferReader reader, out T value);
|
||||
|
||||
public static WriteValueDelegate WriteValue;
|
||||
public static ReadValueDelegate ReadValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is instantiated for types that we can't determine ahead of time are serializable - types
|
||||
/// that don't meet any of the constraints for methods that are available on FastBufferReader and
|
||||
/// FastBufferWriter. These types may or may not be serializable through extension methods. To ensure
|
||||
/// the user has time to pass in the delegates to UserNetworkVariableSerialization, the existence
|
||||
/// of user serialization isn't checked until it's used, so if no serialization is provided, this
|
||||
/// will throw an exception when an object containing the relevant NetworkVariable is spawned.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
|
||||
{
|
||||
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
||||
}
|
||||
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
|
||||
{
|
||||
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
||||
}
|
||||
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NetworkVariableSerializationTypes
|
||||
{
|
||||
internal static readonly HashSet<Type> BaseSupportedTypes = new HashSet<Type>
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(byte),
|
||||
typeof(sbyte),
|
||||
typeof(char),
|
||||
typeof(decimal),
|
||||
typeof(double),
|
||||
typeof(float),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(Vector2),
|
||||
typeof(Vector3),
|
||||
typeof(Vector2Int),
|
||||
typeof(Vector3Int),
|
||||
typeof(Vector4),
|
||||
typeof(Quaternion),
|
||||
typeof(Color),
|
||||
typeof(Color32),
|
||||
typeof(Ray),
|
||||
typeof(Ray2D)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Support methods for reading/writing NetworkVariables
|
||||
/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints,
|
||||
/// but there's no way to achieve the same thing with a class, this includes various read/write delegates
|
||||
/// based on which constraints are met by `T`. These constraints are set up by `NetworkVariableHelpers`,
|
||||
/// which is invoked by code generated by ILPP during module load.
|
||||
/// (As it turns out, IL has support for a module initializer that C# doesn't expose.)
|
||||
/// This installs the correct delegate for each `T` to ensure that each type is serialized properly.
|
||||
///
|
||||
/// Any type that inherits from `NetworkVariableSerialization<T>` will implicitly result in any `T`
|
||||
/// passed to it being picked up and initialized by ILPP.
|
||||
///
|
||||
/// The methods here, despite being static, are `protected` specifically to ensure that anything that
|
||||
/// wants access to them has to inherit from this base class, thus enabling ILPP to find and initialize it.
|
||||
/// but there's no way to achieve the same thing with a class, this sets up various read/write schemes
|
||||
/// based on which constraints are met by `T` using reflection, which is done at module load time.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class NetworkVariableSerialization<T> : NetworkVariableBase where T : unmanaged
|
||||
public static class NetworkVariableSerialization<T> where T : unmanaged
|
||||
{
|
||||
// Functions that know how to serialize INetworkSerializable
|
||||
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializable
|
||||
{
|
||||
writer.WriteNetworkSerializable(value);
|
||||
}
|
||||
private static INetworkVariableSerializer<T> s_Serializer = GetSerializer();
|
||||
|
||||
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializable
|
||||
private static INetworkVariableSerializer<T> GetSerializer()
|
||||
{
|
||||
reader.ReadNetworkSerializable(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize structs
|
||||
internal static void WriteStruct<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
internal static void ReadStruct<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize enums
|
||||
internal static void WriteEnum<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, Enum
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
internal static void ReadEnum<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, Enum
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize other types
|
||||
internal static void WritePrimitive<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
|
||||
internal static void ReadPrimitive<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Should never be reachable at runtime. All calls to this should be replaced with the correct
|
||||
// call above by ILPP.
|
||||
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
{
|
||||
if (value is INetworkSerializable)
|
||||
if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
else if (value is INetworkSerializeByMemcpy)
|
||||
if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
else if (value is Enum)
|
||||
if (typeof(Enum).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
|
||||
|
||||
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
// Obtains "Open Instance Delegates" for the type's NetworkSerialize() methods -
|
||||
// one for an instance of the generic method taking BufferSerializerWriter as T,
|
||||
// one for an instance of the generic method taking BufferSerializerReader as T
|
||||
var writeMethod = (NetworkSerializableSerializer<T>.WriteValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.WriteValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerWriter)));
|
||||
var readMethod = (NetworkSerializableSerializer<T>.ReadValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.ReadValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerReader)));
|
||||
return new NetworkSerializableSerializer<T> { WriteValue = writeMethod, ReadValue = readMethod };
|
||||
}
|
||||
NetworkVariableSerialization<TForMethod>.Write(writer, value);
|
||||
|
||||
if (typeof(IUTF8Bytes).IsAssignableFrom(typeof(T)) && typeof(INativeList<byte>).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
// Get "OpenInstanceDelegates" for the Length property (get and set, which are prefixed
|
||||
// with "get_" and "set_" under the hood and emitted as methods) and GetUnsafePtr()
|
||||
var getLength = (FixedStringSerializer<T>.GetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetLengthDelegate), null, typeof(T).GetMethod("get_" + nameof(INativeList<byte>.Length)));
|
||||
var setLength = (FixedStringSerializer<T>.SetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.SetLengthDelegate), null, typeof(T).GetMethod("set_" + nameof(INativeList<byte>.Length)));
|
||||
var getUnsafePtr = (FixedStringSerializer<T>.GetUnsafePtrDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetUnsafePtrDelegate), null, typeof(T).GetMethod(nameof(IUTF8Bytes.GetUnsafePtr)));
|
||||
return new FixedStringSerializer<T> { GetLength = getLength, SetLength = setLength, GetUnsafePtr = getUnsafePtr };
|
||||
}
|
||||
|
||||
return new FallbackSerializer<T>();
|
||||
}
|
||||
|
||||
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
internal static void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (typeof(Enum).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
|
||||
|
||||
}
|
||||
NetworkVariableSerialization<TForMethod>.Read(reader, out value);
|
||||
s_Serializer.Write(writer, ref value);
|
||||
}
|
||||
|
||||
protected internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
|
||||
|
||||
protected 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.
|
||||
private static WriteDelegate<T> s_Write = WriteValue;
|
||||
private static ReadDelegate<T> s_Read = ReadValue;
|
||||
|
||||
protected static void Write(FastBufferWriter writer, in T value)
|
||||
{
|
||||
s_Write(writer, value);
|
||||
}
|
||||
|
||||
protected static void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
s_Read(reader, out value);
|
||||
}
|
||||
|
||||
internal static void SetWriteDelegate(WriteDelegate<T> write)
|
||||
{
|
||||
s_Write = write;
|
||||
}
|
||||
|
||||
internal static void SetReadDelegate(ReadDelegate<T> read)
|
||||
{
|
||||
s_Read = read;
|
||||
}
|
||||
|
||||
protected NetworkVariableSerialization(
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
internal static void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
s_Serializer.Read(reader, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,14 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal uint SceneEventId;
|
||||
internal Action<uint> EventAction;
|
||||
/// <summary>
|
||||
/// Used server-side for integration testing in order to
|
||||
/// invoke the SceneEventProgress once done loading
|
||||
/// </summary>
|
||||
internal Action Completed;
|
||||
internal void Invoke()
|
||||
{
|
||||
Completed?.Invoke();
|
||||
EventAction.Invoke(SceneEventId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Unity.Netcode
|
||||
/// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
|
||||
/// scene event status.<br/>
|
||||
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// See also: <br/>
|
||||
/// <seealso cref="SceneEventType"/>
|
||||
/// </summary>
|
||||
@@ -166,6 +167,7 @@ namespace Unity.Netcode
|
||||
/// <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>
|
||||
/// <em>Note: Do not start new scene events within NetworkSceneManager scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event SceneEventDelegate OnSceneEvent;
|
||||
|
||||
@@ -239,13 +241,15 @@ namespace Unity.Netcode
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnLoadDelegateHandler OnLoad;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnUnloadDelegateHandler OnUnload;
|
||||
|
||||
@@ -254,7 +258,8 @@ namespace Unity.Netcode
|
||||
/// 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.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.
|
||||
/// This event is generated on a per newly connected and approved client basis.</em>
|
||||
/// This event is generated on a per newly connected and approved client basis.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnSynchronizeDelegateHandler OnSynchronize;
|
||||
|
||||
@@ -263,7 +268,8 @@ namespace Unity.Netcode
|
||||
/// 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
|
||||
/// finished the <see cref="SceneEventType.Load"/> event.<br/>
|
||||
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em>
|
||||
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
|
||||
|
||||
@@ -273,21 +279,24 @@ namespace Unity.Netcode
|
||||
/// 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.<br/>
|
||||
/// <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.</em>
|
||||
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.<br/>
|
||||
/// <em>Note: The server receives this message from all clients (including itself).
|
||||
/// Each client receives their own notification sent to the server.</em>
|
||||
/// Each client receives their own notification sent to the server.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnLoadCompleteDelegateHandler OnLoadComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.<br/>
|
||||
/// <em>Note: The server receives this message from all clients (including itself).
|
||||
/// Each client receives their own notification sent to the server.</em>
|
||||
/// Each client receives their own notification sent to the server.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
|
||||
|
||||
@@ -296,6 +305,7 @@ namespace Unity.Netcode
|
||||
/// <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. This is useful to know that a client has
|
||||
/// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects.</em>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
|
||||
|
||||
@@ -319,8 +329,12 @@ namespace Unity.Netcode
|
||||
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
|
||||
|
||||
/// <summary>
|
||||
/// Proof of concept: Interface version that allows for the decoupling from
|
||||
/// the SceneManager's Load and Unload methods for testing purposes (potentially other future usage)
|
||||
/// The SceneManagerHandler implementation
|
||||
/// </summary>
|
||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||
|
||||
/// <summary>
|
||||
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
|
||||
/// </summary>
|
||||
private class DefaultSceneManagerHandler : ISceneManagerHandler
|
||||
{
|
||||
@@ -339,10 +353,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||
/// End of Proof of Concept
|
||||
|
||||
|
||||
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
|
||||
|
||||
/// <summary>
|
||||
@@ -425,6 +435,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
SceneUnloadEventHandler.Shutdown();
|
||||
|
||||
foreach (var keypair in SceneEventDataStore)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
@@ -632,6 +644,12 @@ namespace Unity.Netcode
|
||||
return validated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for NetcodeIntegrationTest testing in order to properly
|
||||
/// assign the right loaded scene to the right client's ScenesLoaded list
|
||||
/// </summary>
|
||||
internal Func<string, Scene> OverrideGetAndAddNewlyLoadedSceneByName;
|
||||
|
||||
/// <summary>
|
||||
/// Since SceneManager.GetSceneByName only returns the first scene that matches the name
|
||||
/// we must "find" a newly added scene by looking through all loaded scenes and determining
|
||||
@@ -643,20 +661,27 @@ namespace Unity.Netcode
|
||||
/// <returns></returns>
|
||||
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
if (OverrideGetAndAddNewlyLoadedSceneByName != null)
|
||||
{
|
||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||
if (sceneLoaded.name == sceneName)
|
||||
return OverrideGetAndAddNewlyLoadedSceneByName.Invoke(sceneName);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||
if (sceneLoaded.name == sceneName)
|
||||
{
|
||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||
return sceneLoaded;
|
||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
{
|
||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||
return sceneLoaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -720,18 +745,18 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="globalObjectIdHash"></param>
|
||||
/// <returns></returns>
|
||||
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash)
|
||||
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash, int? networkSceneHandle)
|
||||
{
|
||||
if (ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(SceneBeingSynchronized.handle))
|
||||
var sceneHandle = SceneBeingSynchronized.handle;
|
||||
if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0)
|
||||
{
|
||||
var inScenePlacedNetworkObject = ScenePlacedObjects[globalObjectIdHash][SceneBeingSynchronized.handle];
|
||||
|
||||
// We can only have 1 duplicated globalObjectIdHash per scene instance, so remove it once it has been returned
|
||||
ScenePlacedObjects[globalObjectIdHash].Remove(SceneBeingSynchronized.handle);
|
||||
|
||||
return inScenePlacedNetworkObject;
|
||||
sceneHandle = ServerSceneHandleToClientSceneHandle[networkSceneHandle.Value];
|
||||
}
|
||||
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
|
||||
{
|
||||
return ScenePlacedObjects[globalObjectIdHash][sceneHandle];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -859,7 +884,6 @@ namespace Unity.Netcode
|
||||
/// Callback for the <see cref="SceneEventProgress.OnComplete"/> <see cref="SceneEventProgress.OnCompletedDelegate"/> handler
|
||||
/// </summary>
|
||||
/// <param name="sceneEventProgress"></param>
|
||||
/// <returns></returns>
|
||||
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var sceneEventData = BeginSceneEvent();
|
||||
@@ -868,7 +892,7 @@ namespace Unity.Netcode
|
||||
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
|
||||
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
|
||||
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
|
||||
sceneEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList();
|
||||
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList();
|
||||
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
@@ -945,11 +969,18 @@ namespace Unity.Netcode
|
||||
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
|
||||
|
||||
ScenesLoaded.Remove(scene.handle);
|
||||
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded };
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventAction);
|
||||
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
|
||||
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
||||
// If integration testing, IntegrationTestSceneHandler returns null
|
||||
if (sceneUnload == null)
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
||||
}
|
||||
|
||||
// Notify local server that a scene is going to be unloaded
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -1021,6 +1052,12 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private void OnSceneUnloaded(uint sceneEventId)
|
||||
{
|
||||
// If we are shutdown or about to shutdown, then ignore this event
|
||||
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
// First thing we do, if we are a server, is to send the unload scene event.
|
||||
if (m_NetworkManager.IsServer)
|
||||
@@ -1064,7 +1101,7 @@ namespace Unity.Netcode
|
||||
|
||||
private void EmptySceneUnloadedOperation(uint sceneEventId)
|
||||
{
|
||||
// Do nothing (this is a stub call since it is only used to flush all currently loaded scenes)
|
||||
// Do nothing (this is a stub call since it is only used to flush all additively loaded scenes)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1074,6 +1111,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal void UnloadAdditivelyLoadedScenes(uint sceneEventId)
|
||||
{
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
// Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ).
|
||||
var currentActiveScene = SceneManager.GetActiveScene();
|
||||
foreach (var keyHandleEntry in ScenesLoaded)
|
||||
@@ -1083,15 +1121,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
|
||||
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = sceneUnload,
|
||||
SceneEventType = SceneEventType.Unload,
|
||||
SceneName = keyHandleEntry.Value.name,
|
||||
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
|
||||
ClientId = NetworkManager.ServerClientId
|
||||
});
|
||||
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
|
||||
}
|
||||
}
|
||||
// clear out our scenes loaded list
|
||||
@@ -1124,12 +1154,12 @@ namespace Unity.Netcode
|
||||
sceneEventData.SceneEventType = SceneEventType.Load;
|
||||
sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName);
|
||||
sceneEventData.LoadSceneMode = loadSceneMode;
|
||||
|
||||
var sceneEventId = sceneEventData.SceneEventId;
|
||||
// This both checks to make sure the scene is valid and if not resets the active scene event
|
||||
m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
|
||||
if (!m_IsSceneEventActive)
|
||||
{
|
||||
EndSceneEvent(sceneEventData.SceneEventId);
|
||||
EndSceneEvent(sceneEventId);
|
||||
return SceneEventProgressStatus.SceneFailedVerification;
|
||||
}
|
||||
|
||||
@@ -1142,14 +1172,24 @@ namespace Unity.Netcode
|
||||
MoveObjectsToDontDestroyOnLoad();
|
||||
|
||||
// Now Unload all currently additively loaded scenes
|
||||
UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId);
|
||||
UnloadAdditivelyLoadedScenes(sceneEventId);
|
||||
|
||||
// Register the active scene for unload scene event notifications
|
||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
// Now start loading the scene
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneLoaded });
|
||||
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
||||
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded };
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventAction);
|
||||
// If integration testing, IntegrationTestSceneHandler returns null
|
||||
if (sceneLoad == null)
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
||||
}
|
||||
|
||||
// Notify the local server that a scene loading event has begun
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -1167,6 +1207,114 @@ namespace Unity.Netcode
|
||||
return sceneEventProgress.Status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class used to handle "odd ball" scene unload event notification scenarios
|
||||
/// when scene switching.
|
||||
/// </summary>
|
||||
internal class SceneUnloadEventHandler
|
||||
{
|
||||
private static Dictionary<NetworkManager, List<SceneUnloadEventHandler>> s_Instances = new Dictionary<NetworkManager, List<SceneUnloadEventHandler>>();
|
||||
|
||||
internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||
{
|
||||
var networkManager = networkSceneManager.m_NetworkManager;
|
||||
if (!s_Instances.ContainsKey(networkManager))
|
||||
{
|
||||
s_Instances.Add(networkManager, new List<SceneUnloadEventHandler>());
|
||||
}
|
||||
var clientId = networkManager.IsServer ? NetworkManager.ServerClientId : networkManager.LocalClientId;
|
||||
s_Instances[networkManager].Add(new SceneUnloadEventHandler(networkSceneManager, scene, clientId, loadSceneMode, asyncOperation));
|
||||
}
|
||||
|
||||
private static void SceneUnloadComplete(SceneUnloadEventHandler sceneUnloadEventHandler)
|
||||
{
|
||||
if (sceneUnloadEventHandler == null || sceneUnloadEventHandler.m_NetworkSceneManager == null || sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var networkManager = sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager;
|
||||
if (s_Instances.ContainsKey(networkManager))
|
||||
{
|
||||
s_Instances[networkManager].Remove(sceneUnloadEventHandler);
|
||||
if (s_Instances[networkManager].Count == 0)
|
||||
{
|
||||
s_Instances.Remove(networkManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by NetworkSceneManager when it is disposing
|
||||
/// </summary>
|
||||
internal static void Shutdown()
|
||||
{
|
||||
foreach (var instanceEntry in s_Instances)
|
||||
{
|
||||
foreach (var instance in instanceEntry.Value)
|
||||
{
|
||||
instance.OnShutdown();
|
||||
}
|
||||
instanceEntry.Value.Clear();
|
||||
}
|
||||
s_Instances.Clear();
|
||||
}
|
||||
|
||||
private NetworkSceneManager m_NetworkSceneManager;
|
||||
private AsyncOperation m_AsyncOperation;
|
||||
private LoadSceneMode m_LoadSceneMode;
|
||||
private ulong m_ClientId;
|
||||
private Scene m_Scene;
|
||||
private bool m_ShuttingDown;
|
||||
|
||||
private void OnShutdown()
|
||||
{
|
||||
m_ShuttingDown = true;
|
||||
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||
}
|
||||
|
||||
private void SceneUnloaded(Scene scene)
|
||||
{
|
||||
if (m_Scene.handle == scene.handle && !m_ShuttingDown)
|
||||
{
|
||||
if (m_NetworkSceneManager != null && m_NetworkSceneManager.m_NetworkManager != null)
|
||||
{
|
||||
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = m_AsyncOperation,
|
||||
SceneEventType = SceneEventType.UnloadComplete,
|
||||
SceneName = m_Scene.name,
|
||||
LoadSceneMode = m_LoadSceneMode,
|
||||
ClientId = m_ClientId
|
||||
});
|
||||
m_NetworkSceneManager.OnUnloadComplete?.Invoke(m_ClientId, m_Scene.name);
|
||||
}
|
||||
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||
SceneUnloadComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene scene, ulong clientId, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||
{
|
||||
m_LoadSceneMode = loadSceneMode;
|
||||
m_AsyncOperation = asyncOperation;
|
||||
m_NetworkSceneManager = networkSceneManager;
|
||||
m_ClientId = clientId;
|
||||
m_Scene = scene;
|
||||
SceneManager.sceneUnloaded += SceneUnloaded;
|
||||
// Send the initial unload event notification
|
||||
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = m_AsyncOperation,
|
||||
SceneEventType = SceneEventType.Unload,
|
||||
SceneName = m_Scene.name,
|
||||
LoadSceneMode = m_LoadSceneMode,
|
||||
ClientId = clientId
|
||||
});
|
||||
|
||||
m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.m_NetworkManager.LocalClientId, m_Scene.name, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client Side:
|
||||
/// Handles both forms of scene loading
|
||||
@@ -1201,6 +1349,10 @@ namespace Unity.Netcode
|
||||
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
|
||||
{
|
||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||
|
||||
// Register the active scene for unload scene event notifications
|
||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
|
||||
}
|
||||
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
|
||||
@@ -1218,13 +1370,18 @@ namespace Unity.Netcode
|
||||
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Client and Server:
|
||||
/// Generic on scene loaded callback method to be called upon a scene loading
|
||||
/// </summary>
|
||||
private void OnSceneLoaded(uint sceneEventId)
|
||||
{
|
||||
// If we are shutdown or about to shutdown, then ignore this event
|
||||
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash));
|
||||
if (!nextScene.isLoaded || !nextScene.IsValid())
|
||||
@@ -1363,6 +1520,13 @@ namespace Unity.Netcode
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for integration testing, due to the complexities of having all clients loading scenes
|
||||
/// this is needed to "filter" out the scenes not loaded by NetworkSceneManager
|
||||
/// (i.e. we don't want a late joining player to load all of the other client scenes)
|
||||
/// </summary>
|
||||
internal Func<Scene, bool> ExcludeSceneFromSychronization;
|
||||
|
||||
/// <summary>
|
||||
/// Server Side:
|
||||
/// This is used for players that have just had their connection approved and will assure they are synchronized
|
||||
@@ -1389,6 +1553,13 @@ namespace Unity.Netcode
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
|
||||
// NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager
|
||||
// unless the scene in question is the currently active scene.
|
||||
if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var sceneHash = SceneHashFromNameOrPath(scene.path);
|
||||
|
||||
// This would depend upon whether we are additive or not
|
||||
@@ -1406,11 +1577,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sceneEventData.AddSceneToSynchronize(sceneHash, scene.handle);
|
||||
}
|
||||
|
||||
sceneEventData.AddSpawnedNetworkObjects();
|
||||
sceneEventData.AddDespawnedInSceneNetworkObjects();
|
||||
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
@@ -1447,7 +1618,7 @@ namespace Unity.Netcode
|
||||
var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive;
|
||||
|
||||
// Store the sceneHandle and hash
|
||||
sceneEventData.ClientSceneHandle = sceneHandle;
|
||||
sceneEventData.NetworkSceneHandle = sceneHandle;
|
||||
sceneEventData.ClientSceneHash = sceneHash;
|
||||
|
||||
// If this is the beginning of the synchronization event, then send client a notification that synchronization has begun
|
||||
@@ -1536,9 +1707,9 @@ namespace Unity.Netcode
|
||||
SceneManager.SetActiveScene(nextScene);
|
||||
}
|
||||
|
||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.ClientSceneHandle))
|
||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.NetworkSceneHandle))
|
||||
{
|
||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.ClientSceneHandle, nextScene.handle);
|
||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.NetworkSceneHandle, nextScene.handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1862,10 +2033,9 @@ namespace Unity.Netcode
|
||||
foreach (var networkObjectInstance in networkObjects)
|
||||
{
|
||||
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
||||
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
|
||||
var sceneHandle = networkObjectInstance.GetSceneOriginHandle();
|
||||
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
|
||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
|
||||
sceneHandle == sceneToFilterBy.handle)
|
||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && sceneHandle == sceneToFilterBy.handle)
|
||||
{
|
||||
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Unity.Netcode
|
||||
|
||||
// Used by the client during synchronization
|
||||
internal uint ClientSceneHash;
|
||||
internal int ClientSceneHandle;
|
||||
internal int NetworkSceneHandle;
|
||||
|
||||
/// Only used for <see cref="SceneEventType.Synchronize"/> scene events, this assures permissions when writing
|
||||
/// NetworkVariable information. If that process changes, then we need to update this
|
||||
@@ -118,6 +118,9 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private List<NetworkObject> m_NetworkObjectsSync = new List<NetworkObject>();
|
||||
|
||||
private List<NetworkObject> m_DespawnedInSceneObjectsSync = new List<NetworkObject>();
|
||||
private Dictionary<int, List<uint>> m_DespawnedInSceneObjects = new Dictionary<int, List<uint>>();
|
||||
|
||||
/// <summary>
|
||||
/// Server Side Re-Synchronization:
|
||||
/// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned,
|
||||
@@ -243,6 +246,19 @@ namespace Unity.Netcode
|
||||
m_NetworkObjectsSync.Sort(SortNetworkObjects);
|
||||
}
|
||||
|
||||
internal void AddDespawnedInSceneNetworkObjects()
|
||||
{
|
||||
m_DespawnedInSceneObjectsSync.Clear();
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager);
|
||||
foreach (var sobj in inSceneNetworkObjects)
|
||||
{
|
||||
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
|
||||
{
|
||||
m_DespawnedInSceneObjectsSync.Add(sobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server Side:
|
||||
/// Used during the synchronization process to associate NetworkObjects with scenes
|
||||
@@ -372,7 +388,6 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(ScenesToSynchronize.ToArray());
|
||||
writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray());
|
||||
|
||||
|
||||
// Store our current position in the stream to come back and say how much data we have written
|
||||
var positionStart = writer.Position;
|
||||
|
||||
@@ -383,17 +398,31 @@ namespace Unity.Netcode
|
||||
int totalBytes = 0;
|
||||
|
||||
// Write the number of NetworkObjects we are serializing
|
||||
writer.WriteValueSafe(m_NetworkObjectsSync.Count());
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count());
|
||||
// Serialize all NetworkObjects that are spawned
|
||||
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||
writer.WriteValueSafe(m_NetworkObjectsSync[i].gameObject.scene.handle);
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
|
||||
sceneObject.Serialize(writer);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
|
||||
// Write the number of despawned in-scene placed NetworkObjects
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count());
|
||||
// Write the scene handle and GlobalObjectIdHash value
|
||||
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
|
||||
// Size Place Holder -- End
|
||||
var positionEnd = writer.Position;
|
||||
var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint)));
|
||||
@@ -683,15 +712,16 @@ namespace Unity.Netcode
|
||||
{
|
||||
try
|
||||
{
|
||||
// Process all NetworkObjects for this scene
|
||||
InternalBuffer.ReadValueSafe(out int newObjectsCount);
|
||||
// Process all spawned NetworkObjects for this network session
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
|
||||
|
||||
|
||||
for (int i = 0; i < newObjectsCount; i++)
|
||||
{
|
||||
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
|
||||
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
|
||||
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
|
||||
InternalBuffer.ReadValueSafe(out int handle);
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
|
||||
|
||||
var sceneObject = new NetworkObject.SceneObject();
|
||||
@@ -703,6 +733,73 @@ namespace Unity.Netcode
|
||||
m_NetworkObjectsSync.Add(spawnedNetworkObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Process all de-spawned in-scene NetworkObjects for this network session
|
||||
m_DespawnedInSceneObjects.Clear();
|
||||
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
|
||||
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
|
||||
|
||||
for (int i = 0; i < despawnedObjectsCount; i++)
|
||||
{
|
||||
// We just need to get the scene
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
|
||||
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
|
||||
if (!sceneCache.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
|
||||
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
|
||||
{
|
||||
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) =>
|
||||
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
|
||||
|
||||
foreach (var inSceneObject in inSceneNetworkObjects)
|
||||
{
|
||||
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
|
||||
}
|
||||
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
|
||||
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else // Use the cached NetworkObjects if they exist
|
||||
{
|
||||
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
|
||||
}
|
||||
|
||||
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
|
||||
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
// Since this is a NetworkObject that was never spawned, we just need to send a notification
|
||||
// out that it was despawned so users can make adjustments
|
||||
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -61,9 +61,9 @@ namespace Unity.Netcode
|
||||
internal List<ulong> DoneClients { get; } = new List<ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkTime at the moment the scene switch was initiated by the server.
|
||||
/// The local time when the scene event was "roughly started"
|
||||
/// </summary>
|
||||
internal NetworkTime TimeAtInitiation { get; }
|
||||
internal float TimeAtInitiation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
||||
@@ -105,22 +105,40 @@ namespace Unity.Netcode
|
||||
|
||||
internal LoadSceneMode LoadSceneMode;
|
||||
|
||||
internal List<ulong> ClientsThatStartedSceneEvent;
|
||||
|
||||
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
|
||||
{
|
||||
if (status == SceneEventProgressStatus.Started)
|
||||
{
|
||||
// Track the clients that were connected when we started this event
|
||||
ClientsThatStartedSceneEvent = new List<ulong>(networkManager.ConnectedClientsIds);
|
||||
m_NetworkManager = networkManager;
|
||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||
TimeAtInitiation = networkManager.LocalTime;
|
||||
TimeAtInitiation = Time.realtimeSinceStartup;
|
||||
}
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(m_NetworkManager.NetworkConfig.LoadSceneTimeOut);
|
||||
TimedOut = true;
|
||||
CheckCompletion();
|
||||
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
|
||||
while (!TimedOut && !IsCompleted)
|
||||
{
|
||||
yield return waitForNetworkTick;
|
||||
|
||||
CheckCompletion();
|
||||
if (!IsCompleted)
|
||||
{
|
||||
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddClientAsDone(ulong clientId)
|
||||
@@ -141,19 +159,49 @@ namespace Unity.Netcode
|
||||
m_SceneLoadOperation.completed += operation => CheckCompletion();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called only on the server-side during integration test (NetcodeIntegrationTest specific)
|
||||
/// scene loading and unloading.
|
||||
///
|
||||
/// Note: During integration testing we must queue all scene loading and unloading requests for
|
||||
/// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid
|
||||
/// conflicts when the <see cref="SceneManager.sceneLoaded"/> and <see cref="SceneManager.sceneUnloaded"/>
|
||||
/// events are triggered. The Completed action simulates the <see cref="AsyncOperation.completed"/> event.
|
||||
/// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler)
|
||||
/// </summary>
|
||||
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
{
|
||||
sceneEventAction.Completed = SetComplete;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes the SceneEventProgress
|
||||
/// </summary>
|
||||
internal void SetComplete()
|
||||
{
|
||||
IsCompleted = true;
|
||||
AreAllClientsDoneLoading = true;
|
||||
|
||||
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
||||
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
||||
{
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
}
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
|
||||
internal void CheckCompletion()
|
||||
{
|
||||
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && m_SceneLoadOperation.isDone) || (!IsCompleted && TimedOut))
|
||||
try
|
||||
{
|
||||
IsCompleted = true;
|
||||
AreAllClientsDoneLoading = true;
|
||||
|
||||
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
||||
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
||||
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
|
||||
{
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
SetComplete();
|
||||
}
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -75,6 +76,10 @@ namespace Unity.Netcode
|
||||
public void SerializeValue(ref Vector2[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector2Int value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector3Int value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Implementation.SerializeValue(ref value);
|
||||
@@ -88,6 +93,15 @@ namespace Unity.Netcode
|
||||
public void SerializeValue(ref Ray2D value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Ray2D[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value);
|
||||
|
||||
public bool PreCheck(int amount)
|
||||
@@ -107,6 +121,10 @@ namespace Unity.Netcode
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
@@ -119,5 +137,14 @@ namespace Unity.Netcode
|
||||
public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -35,10 +36,18 @@ namespace Unity.Netcode
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValueSafe(out value);
|
||||
|
||||
public void SerializeValue(ref Vector2 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2Int value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3Int value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Reader.ReadValueSafe(out value);
|
||||
@@ -67,10 +76,16 @@ namespace Unity.Netcode
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Reader.ReadValue(out value);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -35,10 +36,17 @@ namespace Unity.Netcode
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValueSafe(value);
|
||||
|
||||
public void SerializeValue(ref Vector2 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2Int value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3Int value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Writer.WriteValueSafe(value);
|
||||
@@ -71,10 +79,17 @@ namespace Unity.Netcode
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValue(value);
|
||||
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Writer.WriteValue(value);
|
||||
|
||||
@@ -54,25 +54,29 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator allocator)
|
||||
private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator copyAllocator, Allocator internalAllocator)
|
||||
{
|
||||
ReaderHandle* readerHandle = null;
|
||||
if (allocator == Allocator.None)
|
||||
if (copyAllocator == Allocator.None)
|
||||
{
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), Allocator.Temp);
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), internalAllocator);
|
||||
readerHandle->BufferPointer = buffer;
|
||||
readerHandle->Position = offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), allocator);
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), copyAllocator);
|
||||
UnsafeUtility.MemCpy(readerHandle + 1, buffer + offset, length);
|
||||
readerHandle->BufferPointer = (byte*)(readerHandle + 1);
|
||||
readerHandle->Position = 0;
|
||||
}
|
||||
|
||||
readerHandle->Length = length;
|
||||
readerHandle->Allocator = allocator;
|
||||
|
||||
// If the copyAllocator provided is Allocator.None, there is a chance that the internalAllocator was provided
|
||||
// When we dispose, we are really only interested in disposing Allocator.Persistent and Allocator.TempJob
|
||||
// as disposing Allocator.Temp and Allocator.None would do nothing. Therefore, make sure we dispose the readerHandle with the right Allocator label
|
||||
readerHandle->Allocator = copyAllocator == Allocator.None ? internalAllocator : copyAllocator;
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
readerHandle->AllowedReadMark = 0;
|
||||
readerHandle->InBitwiseContext = false;
|
||||
@@ -83,23 +87,26 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from a NativeArray.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// Allocator.Temp so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// the context in which it was created (it should neither be returned from that function nor
|
||||
/// stored anywhere in heap memory).
|
||||
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set
|
||||
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||
/// should manually call Dispose() when it is no longer needed.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="allocator"></param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length"></param>
|
||||
/// <param name="offset"></param>
|
||||
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, allocator);
|
||||
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,18 +119,18 @@ namespace Unity.Netcode
|
||||
/// and ensure the FastBufferReader isn't used outside that block.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
|
||||
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0)
|
||||
{
|
||||
if (allocator == Allocator.None)
|
||||
if (copyAllocator == Allocator.None)
|
||||
{
|
||||
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
||||
}
|
||||
fixed (byte* data = buffer.Array)
|
||||
{
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, allocator);
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, copyAllocator, Allocator.Temp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,74 +144,80 @@ namespace Unity.Netcode
|
||||
/// and ensure the FastBufferReader isn't used outside that block.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(byte[] buffer, Allocator allocator, int length = -1, int offset = 0)
|
||||
public unsafe FastBufferReader(byte[] buffer, Allocator copyAllocator, int length = -1, int offset = 0)
|
||||
{
|
||||
if (allocator == Allocator.None)
|
||||
if (copyAllocator == Allocator.None)
|
||||
{
|
||||
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
||||
}
|
||||
fixed (byte* data = buffer)
|
||||
{
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, allocator);
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, copyAllocator, Allocator.Temp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from an existing byte buffer.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// the context in which it was created (it should neither be returned from that function nor
|
||||
/// stored anywhere in heap memory).
|
||||
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set
|
||||
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||
/// should manually call Dispose() when it is no longer needed.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(byte* buffer, Allocator allocator, int length, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(byte* buffer, Allocator copyAllocator, int length, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle(buffer, length, offset, allocator);
|
||||
Handle = CreateHandle(buffer, length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from a FastBufferWriter.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// the context in which it was created (it should neither be returned from that function nor
|
||||
/// stored anywhere in heap memory).
|
||||
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set
|
||||
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||
/// should manually call Dispose() when it is no longer needed.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(FastBufferWriter writer, Allocator allocator, int length = -1, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(FastBufferWriter writer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator);
|
||||
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from another existing FastBufferReader. This is typically used when you
|
||||
/// want to change the allocator that a reader is allocated to - for example, upgrading a Temp reader to
|
||||
/// want to change the copyAllocator that a reader is allocated to - for example, upgrading a Temp reader to
|
||||
/// a Persistent one to be processed later.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
@@ -212,12 +225,13 @@ namespace Unity.Netcode
|
||||
/// stored anywhere in heap memory).
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(FastBufferReader reader, Allocator allocator, int length = -1, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(FastBufferReader reader, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, allocator);
|
||||
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -682,7 +696,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanaged<T>(out T value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanaged<T>(out T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -691,7 +705,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanagedSafe<T>(out T value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanagedSafe<T>(out T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -700,7 +714,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanaged<T>(out T[] value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanaged<T>(out T[] value) where T : unmanaged
|
||||
{
|
||||
ReadUnmanaged(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
@@ -712,7 +726,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanagedSafe<T>(out T[] value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanagedSafe<T>(out T[] value) where T : unmanaged
|
||||
{
|
||||
ReadUnmanagedSafe(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
@@ -724,6 +738,26 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -746,26 +780,6 @@ namespace Unity.Netcode
|
||||
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2 value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -775,6 +789,14 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2Int value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2Int[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3Int value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3Int[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector4 value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector4[] value) => ReadUnmanaged(out value);
|
||||
@@ -808,6 +830,14 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2Int value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2Int[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3Int value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3Int[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector4 value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector4[] value) => ReadUnmanagedSafe(out value);
|
||||
@@ -831,5 +861,31 @@ namespace Unity.Netcode
|
||||
public void ReadValueSafe(out Ray2D value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray2D[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValue<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
ReadUnmanaged(out int length);
|
||||
value = new T();
|
||||
value.Length = length;
|
||||
ReadBytes(value.GetUnsafePtr(), length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValueSafe<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
ReadUnmanagedSafe(out int length);
|
||||
value = new T();
|
||||
value.Length = length;
|
||||
ReadBytesSafe(value.GetUnsafePtr(), length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,15 +716,30 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the size required to write an unmanaged value
|
||||
/// Get the write size for any general unmanaged value
|
||||
/// The ForStructs value here makes this the lowest-priority overload so other versions
|
||||
/// will be prioritized over this if they match
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="unused"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetWriteSize<T>(in T value, ForStructs unused = default) where T : unmanaged
|
||||
{
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the write size for a FixedString
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetWriteSize<T>(in T value) where T : unmanaged
|
||||
public static int GetWriteSize<T>(in T value)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
return sizeof(T);
|
||||
return value.Length + sizeof(int);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -738,7 +753,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanaged<T>(in T value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanaged<T>(in T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -747,7 +762,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanagedSafe<T>(in T value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanagedSafe<T>(in T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -757,7 +772,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanaged<T>(T[] value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanaged<T>(T[] value) where T : unmanaged
|
||||
{
|
||||
WriteUnmanaged(value.Length);
|
||||
fixed (T* ptr = value)
|
||||
@@ -767,7 +782,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanagedSafe<T>(T[] value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanagedSafe<T>(T[] value) where T : unmanaged
|
||||
{
|
||||
WriteUnmanagedSafe(value.Length);
|
||||
fixed (T* ptr = value)
|
||||
@@ -802,22 +817,20 @@ namespace Unity.Netcode
|
||||
|
||||
}
|
||||
|
||||
public struct ForFixedStrings
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -826,17 +839,24 @@ namespace Unity.Netcode
|
||||
public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector2 value) => WriteUnmanaged(value);
|
||||
@@ -847,6 +867,14 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector3[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector2Int value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector2Int[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector3Int value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector3Int[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector4 value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector4[] value) => WriteUnmanaged(value);
|
||||
@@ -870,6 +898,7 @@ namespace Unity.Netcode
|
||||
public void WriteValue(in Ray2D value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Ray2D[] value) => WriteUnmanaged(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -879,6 +908,14 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector2Int value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector2Int[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector3Int value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector3Int[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value);
|
||||
@@ -902,5 +939,36 @@ namespace Unity.Netcode
|
||||
public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValue<T>(in T value, ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
WriteUnmanaged(value.Length);
|
||||
// This avoids a copy on the string, which could be costly for FixedString4096Bytes
|
||||
// Otherwise, GetUnsafePtr() is an impure function call and will result in a copy
|
||||
// for `in` parameters.
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
WriteBytes(ptr->GetUnsafePtr(), value.Length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
if (!TryBeginWriteInternal(sizeof(int) + value.Length))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
WriteValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -21,10 +22,16 @@ namespace Unity.Netcode
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||
void SerializeValue(ref Vector2 value);
|
||||
void SerializeValue(ref Vector2[] value);
|
||||
void SerializeValue(ref Vector3 value);
|
||||
void SerializeValue(ref Vector3[] value);
|
||||
void SerializeValue(ref Vector2Int value);
|
||||
void SerializeValue(ref Vector2Int[] value);
|
||||
void SerializeValue(ref Vector3Int value);
|
||||
void SerializeValue(ref Vector3Int[] value);
|
||||
void SerializeValue(ref Vector4 value);
|
||||
void SerializeValue(ref Vector4[] value);
|
||||
void SerializeValue(ref Quaternion value);
|
||||
@@ -50,11 +57,17 @@ namespace Unity.Netcode
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||
|
||||
void SerializeValuePreChecked(ref Vector2 value);
|
||||
void SerializeValuePreChecked(ref Vector2[] value);
|
||||
void SerializeValuePreChecked(ref Vector3 value);
|
||||
void SerializeValuePreChecked(ref Vector3[] value);
|
||||
void SerializeValuePreChecked(ref Vector2Int value);
|
||||
void SerializeValuePreChecked(ref Vector2Int[] value);
|
||||
void SerializeValuePreChecked(ref Vector3Int value);
|
||||
void SerializeValuePreChecked(ref Vector3Int[] value);
|
||||
void SerializeValuePreChecked(ref Vector4 value);
|
||||
void SerializeValuePreChecked(ref Vector4[] value);
|
||||
void SerializeValuePreChecked(ref Quaternion value);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Unity.Netcode
|
||||
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
|
||||
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
|
||||
{
|
||||
networkBehaviour = (T)GetInternal(this, null);
|
||||
networkBehaviour = GetInternal(this, null) as T;
|
||||
return networkBehaviour != null;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,15 +283,15 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasPrefab(bool isSceneObject, uint globalObjectIdHash)
|
||||
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
||||
{
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
|
||||
{
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, out var networkPrefab))
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab))
|
||||
{
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
@@ -306,14 +306,14 @@ namespace Unity.Netcode
|
||||
|
||||
return false;
|
||||
}
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash);
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle);
|
||||
return networkObject != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should only run on the client
|
||||
/// </summary>
|
||||
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
||||
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, int? networkSceneHandle, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
||||
{
|
||||
NetworkObject parentNetworkObject = null;
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash);
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, networkSceneHandle);
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -477,15 +477,23 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
// this initialization really should be at the bottom of the function
|
||||
networkObject.IsSpawned = true;
|
||||
|
||||
// this initialization really should be at the top of this function. If and when we break the
|
||||
// NetworkVariable dependency on NetworkBehaviour, this otherwise creates problems because
|
||||
// SetNetworkVariableData above calls InitializeVariables, and the 'baked out' data isn't ready there;
|
||||
// the current design banks on getting the network behaviour set and then only reading from it after the
|
||||
// below initialization code. However cowardice compels me to hold off on moving this until that commit
|
||||
networkObject.IsSceneObject = sceneObject;
|
||||
|
||||
// Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects
|
||||
// Note: Always check SceneOriginHandle directly at this specific location.
|
||||
if (networkObject.IsSceneObject != false && networkObject.SceneOriginHandle == 0)
|
||||
{
|
||||
networkObject.SceneOrigin = networkObject.gameObject.scene;
|
||||
}
|
||||
|
||||
// For integration testing, this makes sure that the appropriate NetworkManager is assigned to
|
||||
// the NetworkObject since it uses the NetworkManager.Singleton when not set
|
||||
if (networkObject.NetworkManagerOwner != NetworkManager)
|
||||
{
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
}
|
||||
|
||||
networkObject.NetworkObjectId = networkId;
|
||||
|
||||
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
|
||||
@@ -552,9 +560,8 @@ namespace Unity.Netcode
|
||||
|
||||
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
||||
{
|
||||
//Currently, if this is called and the clientId (destination) is the server's client Id, this case will be checked
|
||||
// within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer placing this check here. [NSS]
|
||||
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
|
||||
// If we are a host and sending to the host's client id, then we can skip sending ourselves the spawn message.
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -785,7 +792,8 @@ namespace Unity.Netcode
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
||||
foreach (var targetClientId in m_TargetClientIds)
|
||||
@@ -803,6 +811,9 @@ namespace Unity.Netcode
|
||||
SpawnedObjectsList.Remove(networkObject);
|
||||
}
|
||||
|
||||
// Always clear out the observers list when despawned
|
||||
networkObject.Observers.Clear();
|
||||
|
||||
var gobj = networkObject.gameObject;
|
||||
if (destroyGameObject && gobj != null)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
Disconnect,
|
||||
|
||||
/// <summary>
|
||||
/// Transport has encountered an unrecoverable failure
|
||||
/// </summary>
|
||||
TransportFailure,
|
||||
|
||||
/// <summary>
|
||||
/// No new event
|
||||
/// </summary>
|
||||
|
||||
@@ -243,6 +243,15 @@ namespace Unity.Netcode.Transports.UTP
|
||||
PacketDropRate = 0
|
||||
};
|
||||
|
||||
private struct PacketLossCache
|
||||
{
|
||||
public int PacketsReceived;
|
||||
public int PacketsDropped;
|
||||
public float PacketLoss;
|
||||
};
|
||||
|
||||
private PacketLossCache m_PacketLossCache = new PacketLossCache();
|
||||
|
||||
private State m_State = State.Disconnected;
|
||||
private NetworkDriver m_Driver;
|
||||
private NetworkSettings m_NetworkSettings;
|
||||
@@ -713,6 +722,15 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
m_Driver.ScheduleUpdate().Complete();
|
||||
|
||||
if (m_ProtocolType == ProtocolType.RelayUnityTransport && m_Driver.GetRelayConnectionStatus() == RelayConnectionStatus.AllocationInvalid)
|
||||
{
|
||||
Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " +
|
||||
"Use NetworkManager.OnTransportFailure to be notified of such events programmatically.");
|
||||
|
||||
InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, Time.realtimeSinceStartup);
|
||||
return;
|
||||
}
|
||||
|
||||
while (AcceptConnection() && m_Driver.IsCreated)
|
||||
{
|
||||
;
|
||||
@@ -839,11 +857,22 @@ namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr();
|
||||
|
||||
var packetReceived = (float)sharedContext->stats.PacketsReceived;
|
||||
var packetDropped = (float)sharedContext->stats.PacketsDropped;
|
||||
var packetLoss = packetReceived > 0 ? packetDropped / packetReceived : 0;
|
||||
var packetReceivedDelta = (float)(sharedContext->stats.PacketsReceived - m_PacketLossCache.PacketsReceived);
|
||||
var packetDroppedDelta = (float)(sharedContext->stats.PacketsDropped - m_PacketLossCache.PacketsDropped);
|
||||
|
||||
return packetLoss;
|
||||
// There can be multiple update happening in a single frame where no packets have transitioned
|
||||
// In those situation we want to return the last packet loss value instead of 0 to avoid invalid swings
|
||||
if (packetDroppedDelta == 0 && packetReceivedDelta == 0)
|
||||
{
|
||||
return m_PacketLossCache.PacketLoss;
|
||||
}
|
||||
|
||||
m_PacketLossCache.PacketsReceived = sharedContext->stats.PacketsReceived;
|
||||
m_PacketLossCache.PacketsDropped = sharedContext->stats.PacketsDropped;
|
||||
|
||||
m_PacketLossCache.PacketLoss = packetReceivedDelta > 0 ? packetDroppedDelta / packetReceivedDelta : 0;
|
||||
|
||||
return m_PacketLossCache.PacketLoss;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1106,6 +1135,8 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
DisposeInternals();
|
||||
|
||||
m_ReliableReceiveQueues.Clear();
|
||||
|
||||
// We must reset this to zero because UTP actually re-uses clientIds if there is a clean disconnect
|
||||
m_ServerClientId = 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user