com.unity.netcode.gameobjects@1.1.0

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.1.0] - 2022-10-21

### Added

- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261)
- `UnityTransport` now provides a way to set the Relay server data directly from the `RelayServerData` structure (provided by the Unity Transport package) throuh its `SetRelayServerData` method. This allows making use of the new APIs in UTP 1.3 that simplify integration of the Relay SDK. (#2235)
- IPv6 is now supported for direct connections when using `UnityTransport`. (#2232)
- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201)
- Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146)
- Added `NetworkObject.TryRemoveParent` method for convenience purposes opposed to having to cast null to either `GameObject` or `NetworkObject`. (#2146)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.0. (#2231)
- The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212)
- As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212)
- The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196)
- `NetworkVariable<>` now supports managed `INetworkSerializable` types, as well as other managed types with serialization/deserialization delegates registered to `UserNetworkVariableSerialization<T>.WriteValue` and `UserNetworkVariableSerialization<T>.ReadValue` (#2219)
- `NetworkVariable<>` and `BufferSerializer<BufferSerializerReader>` now deserialize `INetworkSerializable` types in-place, rather than constructing new ones. (#2219)

### Fixed

- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261)
- Fixed issue caused when changing ownership of objects hidden to some clients (#2242)
- Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239)
- Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226)
- Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225)
- Fixed issue where `SceneEventProgress` would not complete if a client late joins while it is still in progress. (#2222)
- Fixed issue where `SceneEventProgress` would not complete if a client disconnects. (#2222)
- Fixed issues with detecting if a `SceneEventProgress` has timed out. (#2222)
- Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220)
- Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203)
- Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199)
- Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158)
- Fixed warning resulting from a stray NetworkAnimator.meta file (#2153)
- Fixed Connection Approval Timeout not working client side. (#2164)
- Fixed issue where the `WorldPositionStays` parenting parameter was not being synchronized with clients. (#2146)
- Fixed issue where parented in-scene placed `NetworkObject`s would fail for late joining clients. (#2146)
- Fixed issue where scale was not being synchronized which caused issues with nested parenting and scale when `WorldPositionStays` was true. (#2146)
- Fixed issue with `NetworkTransform.ApplyTransformToNetworkStateWithInfo` where it was not honoring axis sync settings when `NetworkTransformState.IsTeleportingNextFrame` was true. (#2146)
- Fixed issue with `NetworkTransform.TryCommitTransformToServer` where it was not honoring the `InLocalSpace` setting. (#2146)
- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144)
- Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List<int>) and extensions for multiple instantiations of that type have been defined (i.e., List<int> and List<string>) (#2142)
- Fixed the issue where running a server (i.e. not host) the second player would not receive updates (unless a third player joined). (#2127)
- Fixed issue where late-joining client transition synchronization could fail when more than one transition was occurring.(#2127)
- Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739)
- Fixed synchronization when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independent from the local time scale. (#2171)
- Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987)
- Fixed IsOwner/IsOwnedByServer being wrong on the server after calling RemoveOwnership (#2211)
This commit is contained in:
Unity Technologies
2022-10-21 00:00:00 +00:00
parent a6969670f5
commit 1e7078c160
97 changed files with 6175 additions and 1643 deletions

View File

@@ -12,3 +12,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")]

View File

@@ -235,14 +235,42 @@ namespace Unity.Netcode
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{
foreach (var client in NetworkManager.ConnectedClients)
if (clientRpcParams.Send.TargetClientIds != null)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
client.Key,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
targetClientId,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
{
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
targetClientId,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
else
{
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
while (observerEnumerator.MoveNext())
{
NetworkManager.NetworkMetrics.TrackRpcSent(
observerEnumerator.Current,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
}
#endif
@@ -436,14 +464,41 @@ namespace Unity.Netcode
IsSpawned = true;
InitializeVariables();
UpdateNetworkProperties();
OnNetworkSpawn();
}
internal void VisibleOnNetworkSpawn()
{
try
{
OnNetworkSpawn();
}
catch (Exception e)
{
Debug.LogException(e);
}
InitializeVariables();
if (IsServer)
{
// Since we just spawned the object and since user code might have modified their NetworkVariable, esp.
// NetworkList, we need to mark the object as free of updates.
// This should happen for all objects on the machine triggering the spawn.
PostNetworkVariableWrite(true);
}
}
internal void InternalOnNetworkDespawn()
{
IsSpawned = false;
UpdateNetworkProperties();
OnNetworkDespawn();
try
{
OnNetworkDespawn();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
/// <summary>
@@ -576,12 +631,24 @@ namespace Unity.Netcode
NetworkVariableIndexesToResetSet.Clear();
}
internal void PostNetworkVariableWrite()
internal void PostNetworkVariableWrite(bool forced = false)
{
// mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
if (forced)
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
// Mark every variable as no longer dirty. We just spawned the object and whatever the game code did
// during OnNetworkSpawn has been sent and needs to be cleared
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].ResetDirty();
}
}
else
{
// mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
}
}
MarkVariablesDirty(false);

View File

@@ -26,6 +26,10 @@ namespace Unity.Netcode
#endif
try
{
// NetworkObject references can become null, when hidden or despawned. Once NUll, there is no point
// trying to process them, even if they were previously marked as dirty.
m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null);
if (networkManager.IsServer)
{
foreach (var dirtyObj in m_DirtyNetworkObjects)
@@ -38,10 +42,6 @@ namespace Unity.Netcode
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
{
var client = networkManager.ConnectedClientsList[i];
if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId)
{
continue;
}
if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
{
@@ -75,10 +75,7 @@ namespace Unity.Netcode
// Now, reset all the no-longer-dirty variables
foreach (var dirtyobj in m_DirtyNetworkObjects)
{
for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++)
{
dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
dirtyobj.PostNetworkVariableWrite();
}
m_DirtyNetworkObjects.Clear();
}

View File

@@ -20,7 +20,7 @@ namespace Unity.Netcode
/// <summary>
/// The main component of the library
/// </summary>
[AddComponentMenu("Netcode/" + nameof(NetworkManager), -100)]
[AddComponentMenu("Netcode/Network Manager", -100)]
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
{
#pragma warning disable IDE1006 // disable naming rule violation check
@@ -51,7 +51,7 @@ namespace Unity.Netcode
internal static string PrefabDebugHelper(NetworkPrefab networkPrefab)
{
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\"";
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.name}\"";
}
internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
@@ -187,8 +187,7 @@ namespace Unity.Netcode
/// <returns>a <see cref="GameObject"/> that is either the override or if no overrides exist it returns the same as the one passed in as a parameter</returns>
public GameObject GetNetworkPrefabOverride(GameObject gameObject)
{
var networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject != null)
if (gameObject.TryGetComponent<NetworkObject>(out var networkObject))
{
if (NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
{
@@ -367,10 +366,22 @@ namespace Unity.Netcode
public bool IsListening { get; internal set; }
/// <summary>
/// Gets if we are connected as a client
/// When true, the client is connected, approved, and synchronized with
/// the server.
/// </summary>
public bool IsConnectedClient { get; internal set; }
/// <summary>
/// Is true when the client has been approved.
/// </summary>
/// <remarks>
/// This only reflects the client's approved status and does not mean the client
/// has finished the connection and synchronization process. The server-host will
/// always be approved upon being starting the <see cref="NetworkManager"/>
/// <see cref="IsConnectedClient"/>
/// </remarks>
public bool IsApproved { get; internal set; }
/// <summary>
/// Can be used to determine if the <see cref="NetworkManager"/> is currently shutting itself down
/// </summary>
@@ -519,8 +530,7 @@ namespace Unity.Netcode
var networkPrefabGo = networkPrefab?.Prefab;
if (networkPrefabGo != null)
{
var networkObject = networkPrefabGo.GetComponent<NetworkObject>();
if (networkObject == null)
if (!networkPrefabGo.TryGetComponent<NetworkObject>(out var networkObject))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
@@ -695,8 +705,7 @@ namespace Unity.Netcode
}
else if (networkPrefab.Override == NetworkPrefabOverride.None)
{
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
if (networkObject == null)
if (!networkPrefab.Prefab.TryGetComponent(out networkObject))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
@@ -742,8 +751,7 @@ namespace Unity.Netcode
}
else
{
networkObject = networkPrefab.SourcePrefabToOverride.GetComponent<NetworkObject>();
if (networkObject == null)
if (!networkPrefab.SourcePrefabToOverride.TryGetComponent(out networkObject))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
@@ -881,6 +889,8 @@ namespace Unity.Netcode
return;
}
IsApproved = false;
ComponentFactory.SetDefaults();
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -968,8 +978,7 @@ namespace Unity.Netcode
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
if (NetworkConfig.PlayerPrefab != null)
{
var playerPrefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>();
if (playerPrefabNetworkObject != null)
if (NetworkConfig.PlayerPrefab.TryGetComponent<NetworkObject>(out var playerPrefabNetworkObject))
{
//In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject
@@ -1023,24 +1032,38 @@ namespace Unity.Netcode
}
Initialize(true);
IsServer = true;
IsClient = false;
IsListening = true;
// If we failed to start then shutdown and notify user that the transport failed to start
if (NetworkConfig.NetworkTransport.StartServer())
try
{
IsServer = true;
IsClient = false;
IsListening = true;
// If we failed to start then shutdown and notify user that the transport failed to start
if (NetworkConfig.NetworkTransport.StartServer())
{
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
OnServerStarted?.Invoke();
IsApproved = true;
return true;
}
else
{
IsServer = false;
IsClient = false;
IsListening = false;
OnServerStarted?.Invoke();
return true;
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
OnTransportFailure?.Invoke();
Shutdown();
}
}
else
catch (Exception)
{
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
OnTransportFailure?.Invoke();
Shutdown();
IsServer = false;
IsClient = false;
IsListening = false;
throw;
}
return false;
@@ -1098,23 +1121,38 @@ namespace Unity.Netcode
Initialize(true);
// If we failed to start then shutdown and notify user that the transport failed to start
if (!NetworkConfig.NetworkTransport.StartServer())
IsServer = true;
IsClient = true;
IsListening = true;
try
{
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
OnTransportFailure?.Invoke();
Shutdown();
return false;
// If we failed to start then shutdown and notify user that the transport failed to start
if (!NetworkConfig.NetworkTransport.StartServer())
{
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
OnTransportFailure?.Invoke();
Shutdown();
IsServer = false;
IsClient = false;
IsListening = false;
return false;
}
}
catch (Exception)
{
IsServer = false;
IsClient = false;
IsListening = false;
throw;
}
MessagingSystem.ClientConnected(ServerClientId);
LocalClientId = ServerClientId;
NetworkMetrics.SetConnectionId(LocalClientId);
IsServer = true;
IsClient = true;
IsListening = true;
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
{
var response = new ConnectionApprovalResponse();
@@ -1128,6 +1166,7 @@ namespace Unity.Netcode
}
response.Approved = true;
IsApproved = true;
HandleConnectionApproval(ServerClientId, response);
}
else
@@ -1389,6 +1428,7 @@ namespace Unity.Netcode
}
IsConnectedClient = false;
IsApproved = false;
// We need to clean up NetworkObjects before we reset the IsServer
// and IsClient properties. This provides consistency of these two
@@ -1560,7 +1600,7 @@ namespace Unity.Netcode
}
// Only update RTT here, server time is updated by time sync messages
var reset = NetworkTimeSystem.Advance(Time.deltaTime);
var reset = NetworkTimeSystem.Advance(Time.unscaledDeltaTime);
if (reset)
{
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
@@ -1569,7 +1609,7 @@ namespace Unity.Netcode
if (IsServer == false)
{
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.deltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.unscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
}
}
@@ -1614,7 +1654,9 @@ namespace Unity.Netcode
{
var message = new ConnectionRequestMessage
{
ConfigHash = NetworkConfig.GetConfig(),
// Since only a remote client will send a connection request,
// we should always force the rebuilding of the NetworkConfig hash value
ConfigHash = NetworkConfig.GetConfig(false),
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
ConnectionData = NetworkConfig.ConnectionData
};
@@ -1623,23 +1665,73 @@ namespace Unity.Netcode
private IEnumerator ApprovalTimeout(ulong clientId)
{
NetworkTime timeStarted = LocalTime;
var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup;
var timedOut = false;
var connectionApproved = false;
var connectionNotApproved = false;
var timeoutMarker = timeStarted + NetworkConfig.ClientConnectionBufferTimeout;
//We yield every frame incase a pending client disconnects and someone else gets its connection id
while ((LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId))
while (IsListening && !ShutdownInProgress && !timedOut && !connectionApproved)
{
yield return null;
// Check if we timed out
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup);
if (IsServer)
{
// When the client is no longer in the pending clients list and is in the connected clients list
// it has been approved
connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId);
// For the server side, if the client is in neither list then it was declined or the client disconnected
connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId);
}
else
{
connectionApproved = IsApproved;
}
}
if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId))
// Exit coroutine if we are no longer listening or a shutdown is in progress (client or server)
if (!IsListening || ShutdownInProgress)
{
yield break;
}
// If the client timed out or was not approved
if (timedOut || connectionNotApproved)
{
// Timeout
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out");
if (timedOut)
{
if (IsServer)
{
// Log a warning that the transport detected a connection but then did not receive a follow up connection request message.
// (hacking or something happened to the server's network connection)
NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message.");
}
else
{
// We only provide informational logging for the client side
NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request.");
}
}
else if (connectionNotApproved)
{
NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved.");
}
}
DisconnectClient(clientId);
if (IsServer)
{
DisconnectClient(clientId);
}
else
{
Shutdown(true);
}
}
}
@@ -1916,13 +2008,20 @@ namespace Unity.Netcode
var playerObject = networkClient.PlayerObject;
if (playerObject != null)
{
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
if (!playerObject.DontDestroyWithOwner)
{
PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
{
PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
}
else
{
Destroy(playerObject.gameObject);
}
}
else
{
Destroy(playerObject.gameObject);
playerObject.RemoveOwnership();
}
}
@@ -2033,14 +2132,31 @@ namespace Unity.Netcode
if (response.CreatePlayerObject)
{
var networkObject = SpawnManager.CreateLocalNetworkObject(
isSceneObject: false,
response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash,
ownerClientId,
parentNetworkId: null,
networkSceneHandle: null,
response.Position,
response.Rotation);
var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
// Generate a SceneObject for the player object to spawn
var sceneObject = new NetworkObject.SceneObject
{
Header = new NetworkObject.SceneObject.HeaderData
{
IsPlayerObject = true,
OwnerClientId = ownerClientId,
IsSceneObject = false,
HasTransform = true,
Hash = playerPrefabHash,
},
TargetClientId = ownerClientId,
Transform = new NetworkObject.SceneObject.TransformData
{
Position = response.Position.GetValueOrDefault(),
Rotation = response.Rotation.GetValueOrDefault()
}
};
// Create the player NetworkObject locally
var networkObject = SpawnManager.CreateLocalNetworkObject(sceneObject);
// Spawn the player NetworkObject locally
SpawnManager.SpawnNetworkObjectLocally(
networkObject,
SpawnManager.GetNetworkObjectId(),

View File

@@ -9,7 +9,7 @@ namespace Unity.Netcode
/// <summary>
/// A component used to identify that a GameObject in the network
/// </summary>
[AddComponentMenu("Netcode/" + nameof(NetworkObject), -99)]
[AddComponentMenu("Netcode/Network Object", -99)]
[DisallowMultipleComponent]
public sealed class NetworkObject : MonoBehaviour
{
@@ -129,7 +129,7 @@ namespace Unity.Netcode
/// <summary>
/// Whether or not to destroy this object if it's owner is destroyed.
/// If false, the objects ownership will be given to the server.
/// If true, the objects ownership will be given to the server.
/// </summary>
public bool DontDestroyWithOwner;
@@ -435,7 +435,7 @@ namespace Unity.Netcode
private void OnDestroy()
{
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned &&
(IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
(IsSceneObject == null || (IsSceneObject.Value != true)))
{
throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
}
@@ -509,6 +509,7 @@ namespace Unity.Netcode
/// <param name="destroy">(true) the <see cref="GameObject"/> will be destroyed (false) the <see cref="GameObject"/> will persist after being despawned</param>
public void Despawn(bool destroy = true)
{
MarkVariablesDirty(false);
NetworkManager.SpawnManager.DespawnObject(this, destroy);
}
@@ -572,21 +573,21 @@ namespace Unity.Netcode
}
}
private bool m_IsReparented; // Did initial parent (came from the scene hierarchy) change at runtime?
private ulong? m_LatestParent; // What is our last set parent NetworkObject's ID?
private Transform m_CachedParent; // What is our last set parent Transform reference?
private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent
internal void SetCachedParent(Transform parentTransform)
{
m_CachedParent = parentTransform;
}
internal (bool IsReparented, ulong? LatestParent) GetNetworkParenting() => (m_IsReparented, m_LatestParent);
internal ulong? GetNetworkParenting() => m_LatestParent;
internal void SetNetworkParenting(bool isReparented, ulong? latestParent)
internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays)
{
m_IsReparented = isReparented;
m_LatestParent = latestParent;
m_CachedWorldPositionStays = worldPositionStays;
}
/// <summary>
@@ -597,7 +598,10 @@ namespace Unity.Netcode
/// <returns>Whether or not reparenting was successful.</returns>
public bool TrySetParent(Transform parent, bool worldPositionStays = true)
{
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
var networkObject = parent.GetComponent<NetworkObject>();
// If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent
return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays);
}
/// <summary>
@@ -608,7 +612,37 @@ namespace Unity.Netcode
/// <returns>Whether or not reparenting was successful.</returns>
public bool TrySetParent(GameObject parent, bool worldPositionStays = true)
{
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
// If we are removing ourself from a parent
if (parent == null)
{
return TrySetParent((NetworkObject)null, worldPositionStays);
}
var networkObject = parent.GetComponent<NetworkObject>();
// If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent
return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays);
}
/// <summary>
/// Used when despawning the parent, we want to preserve the cached WorldPositionStays value
/// </summary>
internal bool TryRemoveParentCachedWorldPositionStays()
{
return TrySetParent((NetworkObject)null, m_CachedWorldPositionStays);
}
/// <summary>
/// Removes the parent of the NetworkObject's transform
/// </summary>
/// <remarks>
/// This is a more convenient way to remove the parent without having to cast the null value to either <see cref="GameObject"/> or <see cref="NetworkObject"/>
/// </remarks>
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
/// <returns></returns>
public bool TryRemoveParent(bool worldPositionStays = true)
{
return TrySetParent((NetworkObject)null, worldPositionStays);
}
/// <summary>
@@ -639,17 +673,21 @@ namespace Unity.Netcode
return false;
}
if (parent != null && !parent.IsSpawned)
{
return false;
}
m_CachedWorldPositionStays = worldPositionStays;
if (parent == null)
{
return false;
transform.SetParent(null, worldPositionStays);
}
if (!parent.IsSpawned)
else
{
return false;
transform.SetParent(parent.transform, worldPositionStays);
}
transform.SetParent(parent.transform, worldPositionStays);
return true;
}
@@ -685,12 +723,11 @@ namespace Unity.Netcode
Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned"));
return;
}
var removeParent = false;
var parentTransform = transform.parent;
if (parentTransform != null)
{
var parentObject = transform.parent.GetComponent<NetworkObject>();
if (parentObject == null)
if (!transform.parent.TryGetComponent<NetworkObject>(out var parentObject))
{
transform.parent = m_CachedParent;
Debug.LogException(new InvalidParentException($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent"));
@@ -709,19 +746,31 @@ namespace Unity.Netcode
else
{
m_LatestParent = null;
removeParent = m_CachedParent != null;
}
m_IsReparented = true;
ApplyNetworkParenting();
ApplyNetworkParenting(removeParent);
var message = new ParentSyncMessage
{
NetworkObjectId = NetworkObjectId,
IsReparented = m_IsReparented,
IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue,
LatestParent = m_LatestParent
LatestParent = m_LatestParent,
RemoveParent = removeParent,
WorldPositionStays = m_CachedWorldPositionStays,
Position = m_CachedWorldPositionStays ? transform.position : transform.localPosition,
Rotation = m_CachedWorldPositionStays ? transform.rotation : transform.localRotation,
Scale = transform.localScale,
};
// We need to preserve the m_CachedWorldPositionStays value until after we create the message
// in order to assure any local space values changed/reset get applied properly. If our
// parent is null then go ahead and reset the m_CachedWorldPositionStays the default value.
if (parentTransform == null)
{
m_CachedWorldPositionStays = true;
}
unsafe
{
var maxCount = NetworkManager.ConnectedClientsIds.Count;
@@ -749,42 +798,90 @@ namespace Unity.Netcode
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
internal bool ApplyNetworkParenting()
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false)
{
if (!AutoObjectParentSync)
{
return false;
}
if (!IsSpawned)
// SPECIAL CASE:
// The ignoreNotSpawned is a special case scenario where a late joining client has joined
// and loaded one or more scenes that contain nested in-scene placed NetworkObject children
// yet the server's synchronization information does not indicate the NetworkObject in question
// has a parent. Under this scenario, we want to remove the parent before spawning and setting
// the transform values. This is the only scenario where the ignoreNotSpawned parameter is used.
if (!IsSpawned && !ignoreNotSpawned)
{
return false;
}
if (!m_IsReparented)
// Handle the first in-scene placed NetworkObject parenting scenarios. Once the m_LatestParent
// has been set, this will not be entered into again (i.e. the later code will be invoked and
// users will get notifications when the parent changes).
var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value;
if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && isInScenePlaced)
{
return true;
var parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
// If parentNetworkObject is null then the parent is a GameObject without a NetworkObject component
// attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting.
// Note: We only start tracking parenting if the user removes the child from the standard GameObject
// parent and then re-parents the child under a GameObject with a NetworkObject component attached.
if (parentNetworkObject == null)
{
return true;
}
else // If the parent still isn't spawned add this to the orphaned children and return false
if (!parentNetworkObject.IsSpawned)
{
OrphanChildren.Add(this);
return false;
}
else
{
// If we made it this far, go ahead and set the network parenting values
// with the default WorldPoisitonSays value
SetNetworkParenting(parentNetworkObject.NetworkObjectId, true);
// Set the cached parent
m_CachedParent = parentNetworkObject.transform;
return true;
}
}
if (m_LatestParent == null || !m_LatestParent.HasValue)
// If we are removing the parent or our latest parent is not set, then remove the parent
// removeParent is only set when:
// - The server-side NetworkObject.OnTransformParentChanged is invoked and the parent is being removed
// - The client-side when handling a ParentSyncMessage
// When clients are synchronizing only the m_LatestParent.HasValue will not have a value if there is no parent
// or a parent was removed prior to the client connecting (i.e. in-scene placed NetworkObjects)
if (removeParent || !m_LatestParent.HasValue)
{
m_CachedParent = null;
transform.parent = null;
// We must use Transform.SetParent when taking WorldPositionStays into
// consideration, otherwise just setting transform.parent = null defaults
// to WorldPositionStays which can cause scaling issues if the parent's
// scale is not the default (Vetctor3.one) value.
transform.SetParent(null, m_CachedWorldPositionStays);
InvokeBehaviourOnNetworkObjectParentChanged(null);
return true;
}
if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
// If we have a latest parent id but it hasn't been spawned yet, then add this instance to the orphanChildren
// HashSet and return false (i.e. parenting not applied yet)
if (m_LatestParent.HasValue && !NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
{
OrphanChildren.Add(this);
return false;
}
// If we made it here, then parent this instance under the parentObject
var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value];
m_CachedParent = parentObject.transform;
transform.parent = parentObject.transform;
transform.SetParent(parentObject.transform, m_CachedWorldPositionStays);
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
return true;
@@ -821,6 +918,13 @@ namespace Unity.Netcode
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!");
}
}
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].VisibleOnNetworkSpawn();
}
}
}
internal void InvokeBehaviourNetworkDespawn()
@@ -962,7 +1066,6 @@ namespace Unity.Netcode
public bool HasParent;
public bool IsSceneObject;
public bool HasTransform;
public bool IsReparented;
}
public HeaderData Header;
@@ -975,6 +1078,7 @@ namespace Unity.Netcode
{
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
}
public TransformData Transform;
@@ -990,12 +1094,19 @@ namespace Unity.Netcode
public int NetworkSceneHandle;
public bool WorldPositionStays;
public unsafe void Serialize(FastBufferWriter writer)
{
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;
if (Header.HasParent)
{
writeSize += FastBufferWriter.GetWriteSize(ParentObjectId);
writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
}
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
if (!writer.TryBeginWrite(writeSize))
@@ -1008,6 +1119,12 @@ namespace Unity.Netcode
if (Header.HasParent)
{
writer.WriteValue(ParentObjectId);
writer.WriteValue(WorldPositionStays);
writer.WriteValue(IsLatestParentSet);
if (IsLatestParentSet)
{
writer.WriteValue(LatestParent.Value);
}
}
if (Header.HasTransform)
@@ -1015,15 +1132,6 @@ namespace Unity.Netcode
writer.WriteValue(Transform);
}
if (Header.IsReparented)
{
writer.WriteValue(IsLatestParentSet);
if (IsLatestParentSet)
{
writer.WriteValue((ulong)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.
@@ -1044,19 +1152,41 @@ namespace Unity.Netcode
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
}
reader.ReadValue(out Header);
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;
var readSize = 0;
if (Header.HasParent)
{
readSize += FastBufferWriter.GetWriteSize(ParentObjectId);
readSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
// We need to read at this point in order to get the IsLatestParentSet value
if (!reader.TryBeginRead(readSize))
{
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
}
// Read the initial parenting related properties
reader.ReadValue(out ParentObjectId);
reader.ReadValue(out WorldPositionStays);
reader.ReadValue(out IsLatestParentSet);
// Now calculate the remaining bytes to read
readSize = 0;
readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
}
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
// Try to begin reading the remaining bytes
if (!reader.TryBeginRead(readSize))
{
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
}
if (Header.HasParent)
if (IsLatestParentSet)
{
reader.ReadValue(out ParentObjectId);
reader.ReadValueSafe(out ulong latestParent);
LatestParent = latestParent;
}
if (Header.HasTransform)
@@ -1064,16 +1194,6 @@ namespace Unity.Netcode
reader.ReadValue(out Transform);
}
if (Header.IsReparented)
{
reader.ReadValue(out IsLatestParentSet);
if (IsLatestParentSet)
{
reader.ReadValueSafe(out ulong latestParent);
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.
@@ -1086,6 +1206,14 @@ namespace Unity.Netcode
}
}
internal void PostNetworkVariableWrite()
{
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
{
ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
}
internal SceneObject GetMessageSceneObject(ulong targetClientId)
{
var obj = new SceneObject
@@ -1109,25 +1237,12 @@ namespace Unity.Netcode
parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
}
if (parentNetworkObject)
if (parentNetworkObject != null)
{
obj.Header.HasParent = true;
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
}
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
{
obj.Header.HasTransform = true;
obj.Transform = new SceneObject.TransformData
{
Position = transform.position,
Rotation = transform.rotation
};
}
var (isReparented, latestParent) = GetNetworkParenting();
obj.Header.IsReparented = isReparented;
if (isReparented)
{
obj.WorldPositionStays = m_CachedWorldPositionStays;
var latestParent = GetNetworkParenting();
var isLatestParentSet = latestParent != null && latestParent.HasValue;
obj.IsLatestParentSet = isLatestParentSet;
if (isLatestParentSet)
@@ -1136,6 +1251,25 @@ namespace Unity.Netcode
}
}
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
{
obj.Header.HasTransform = true;
obj.Transform = new SceneObject.TransformData
{
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
// values as opposed world space values.
Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position,
Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation,
// We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can
// impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale
// which can be thought of as "world space scale".
// More information:
// https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html
Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale,
};
}
return obj;
}
@@ -1149,33 +1283,8 @@ namespace Unity.Netcode
/// <returns>optional to use NetworkObject deserialized</returns>
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader variableData, NetworkManager networkManager)
{
Vector3? position = null;
Quaternion? rotation = null;
ulong? parentNetworkId = null;
int? networkSceneHandle = null;
if (sceneObject.Header.HasTransform)
{
position = sceneObject.Transform.Position;
rotation = sceneObject.Transform.Rotation;
}
if (sceneObject.Header.HasParent)
{
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, networkSceneHandle, position, rotation, sceneObject.Header.IsReparented);
networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent);
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
if (networkObject == null)
{
@@ -1190,7 +1299,7 @@ namespace Unity.Netcode
return null;
}
// Spawn the NetworkObject(
// Spawn the NetworkObject
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false);
return networkObject;

View File

@@ -79,6 +79,7 @@ namespace Unity.Netcode
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
networkManager.IsApproved = true;
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
if (!networkManager.NetworkConfig.EnableSceneManagement)

View File

@@ -1,10 +1,12 @@
using UnityEngine;
namespace Unity.Netcode
{
internal struct ParentSyncMessage : INetworkMessage
{
public ulong NetworkObjectId;
public bool IsReparented;
public bool WorldPositionStays;
//If(Metadata.IsReparented)
public bool IsLatestParentSet;
@@ -12,18 +14,36 @@ namespace Unity.Netcode
//If(IsLatestParentSet)
public ulong? LatestParent;
// Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent)
public bool RemoveParent;
// These additional properties are used to synchronize clients with the current position,
// rotation, and scale after parenting/de-parenting (world/local space relative). This
// allows users to control the final child's transform values without having to have a
// NetworkTransform component on the child. (i.e. picking something up)
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
public void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(NetworkObjectId);
writer.WriteValueSafe(IsReparented);
if (IsReparented)
BytePacker.WriteValuePacked(writer, NetworkObjectId);
writer.WriteValueSafe(RemoveParent);
writer.WriteValueSafe(WorldPositionStays);
if (!RemoveParent)
{
writer.WriteValueSafe(IsLatestParentSet);
if (IsLatestParentSet)
{
writer.WriteValueSafe((ulong)LatestParent);
BytePacker.WriteValueBitPacked(writer, (ulong)LatestParent);
}
}
// Whether parenting or removing a parent, we always update the position, rotation, and scale
writer.WriteValueSafe(Position);
writer.WriteValueSafe(Rotation);
writer.WriteValueSafe(Scale);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
@@ -34,24 +54,30 @@ namespace Unity.Netcode
return false;
}
reader.ReadValueSafe(out NetworkObjectId);
reader.ReadValueSafe(out IsReparented);
if (IsReparented)
ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out RemoveParent);
reader.ReadValueSafe(out WorldPositionStays);
if (!RemoveParent)
{
reader.ReadValueSafe(out IsLatestParentSet);
if (IsLatestParentSet)
{
reader.ReadValueSafe(out ulong latestParent);
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
LatestParent = latestParent;
}
}
// Whether parenting or removing a parent, we always update the position, rotation, and scale
reader.ReadValueSafe(out Position);
reader.ReadValueSafe(out Rotation);
reader.ReadValueSafe(out Scale);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
return false;
}
return true;
}
@@ -59,8 +85,22 @@ namespace Unity.Netcode
{
var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
networkObject.SetNetworkParenting(IsReparented, LatestParent);
networkObject.ApplyNetworkParenting();
networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
networkObject.ApplyNetworkParenting(RemoveParent);
// We set all of the transform values after parenting as they are
// the values of the server-side post-parenting transform values
if (!WorldPositionStays)
{
networkObject.transform.localPosition = Position;
networkObject.transform.localRotation = Rotation;
}
else
{
networkObject.transform.position = Position;
networkObject.transform.rotation = Rotation;
}
networkObject.transform.localScale = Scale;
}
}
}

View File

@@ -5,17 +5,14 @@ using Unity.Multiplayer.Tools;
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats;
using Unity.Profiling;
using UnityEngine;
namespace Unity.Netcode
{
internal class NetworkMetrics : INetworkMetrics
{
const ulong k_MaxMetricsPerFrame = 1000L;
static Dictionary<uint, string> s_SceneEventTypeNames;
static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
private const ulong k_MaxMetricsPerFrame = 1000L;
private static Dictionary<uint, string> s_SceneEventTypeNames;
private static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
static NetworkMetrics()
{
@@ -531,7 +528,7 @@ namespace Unity.Netcode
}
}
internal class NetcodeObserver
internal class NetcodeObserver
{
public static IMetricObserver Observer { get; } = MetricObserverFactory.Construct();
}

View File

@@ -4,7 +4,7 @@ using UnityEngine;
namespace Unity.Netcode
{
class NetworkObjectProvider : INetworkObjectProvider
internal class NetworkObjectProvider : INetworkObjectProvider
{
private readonly NetworkManager m_NetworkManager;
@@ -15,7 +15,7 @@ namespace Unity.Netcode
public Object GetNetworkObject(ulong networkObjectId)
{
if(m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
if (m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
{
return value;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
@@ -11,7 +12,6 @@ namespace Unity.Netcode
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);
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
/// <summary>
@@ -39,9 +39,13 @@ namespace Unity.Netcode
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
{
foreach (var value in values)
// allow null IEnumerable<T> to mean "no values"
if (values != null)
{
m_List.Add(value);
foreach (var value in values)
{
m_List.Add(value);
}
}
}
@@ -52,7 +56,6 @@ namespace Unity.Netcode
if (m_DirtyEvents.Length > 0)
{
m_DirtyEvents.Clear();
m_ListAtLastReset.CopyFrom(m_List);
}
}
@@ -65,6 +68,13 @@ namespace Unity.Netcode
internal void MarkNetworkObjectDirty()
{
if (m_NetworkBehaviour == null)
{
Debug.LogWarning($"NetworkList is written to, but doesn't know its NetworkBehaviour yet. " +
"Are you modifying a NetworkList before the NetworkObject is spawned?");
return;
}
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
@@ -127,26 +137,10 @@ namespace Unity.Netcode
/// <inheritdoc />
public override void WriteField(FastBufferWriter writer)
{
// The listAtLastReset mechanism was put in place to deal with duplicate adds
// upon initial spawn. However, it causes issues with in-scene placed objects
// due to difference in spawn order. In order to address this, we pick the right
// list based on the type of object.
bool isSceneObject = m_NetworkBehaviour.NetworkObject.IsSceneObject != false;
if (isSceneObject)
writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++)
{
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
for (int i = 0; i < m_ListAtLastReset.Length; i++)
{
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
}
}
else
{
writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++)
{
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
}
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
}
}
@@ -157,7 +151,8 @@ namespace Unity.Netcode
reader.ReadValueSafe(out ushort count);
for (int i = 0; i < count; i++)
{
NetworkVariableSerialization<T>.Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
m_List.Add(value);
}
}
@@ -173,7 +168,8 @@ namespace Unity.Netcode
{
case NetworkListEvent<T>.EventType.Add:
{
NetworkVariableSerialization<T>.Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
m_List.Add(value);
if (OnListChanged != null)
@@ -201,7 +197,8 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Insert:
{
reader.ReadValueSafe(out int index);
NetworkVariableSerialization<T>.Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
if (index < m_List.Length)
{
@@ -237,7 +234,8 @@ namespace Unity.Netcode
break;
case NetworkListEvent<T>.EventType.Remove:
{
NetworkVariableSerialization<T>.Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
int index = m_List.IndexOf(value);
if (index == -1)
{
@@ -299,7 +297,8 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Value:
{
reader.ReadValueSafe(out int index);
NetworkVariableSerialization<T>.Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
if (index >= m_List.Length)
{
throw new Exception("Shouldn't be here, index is higher than list length");
@@ -374,6 +373,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public void Add(T item)
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.Add(item);
var listEvent = new NetworkListEvent<T>()
@@ -389,6 +394,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public void Clear()
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.Clear();
var listEvent = new NetworkListEvent<T>()
@@ -409,6 +420,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public bool Remove(T item)
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
int index = NativeArrayExtensions.IndexOf(m_List, item);
if (index == -1)
{
@@ -438,6 +455,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public void Insert(int index, T item)
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
if (index < m_List.Length)
{
m_List.InsertRangeWithBeginEnd(index, index + 1);
@@ -461,6 +484,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public void RemoveAt(int index)
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.RemoveAt(index);
var listEvent = new NetworkListEvent<T>()
@@ -478,6 +507,12 @@ namespace Unity.Netcode
get => m_List[index];
set
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
var previousValue = m_List[index];
m_List[index] = value;
@@ -520,7 +555,6 @@ namespace Unity.Netcode
public override void Dispose()
{
m_List.Dispose();
m_ListAtLastReset.Dispose();
m_DirtyEvents.Dispose();
}
}

View File

@@ -1,7 +1,5 @@
using UnityEngine;
using System;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
@@ -10,7 +8,7 @@ namespace Unity.Netcode
/// </summary>
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
[Serializable]
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
public class NetworkVariable<T> : NetworkVariableBase
{
/// <summary>
/// Delegate type for value changed event
@@ -52,7 +50,7 @@ namespace Unity.Netcode
set
{
// Compare bitwise
if (ValueEquals(ref m_InternalValue, ref value))
if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
{
return;
}
@@ -66,20 +64,6 @@ namespace Unity.Netcode
}
}
// Compares two values of the same unmanaged type by underlying memory
// Ignoring any overridden value checks
// Size is fixed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe bool ValueEquals(ref T a, ref T b)
{
// get unmanaged pointers
var aptr = UnsafeUtility.AddressOf(ref a);
var bptr = UnsafeUtility.AddressOf(ref b);
// compare addresses
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
}
/// <summary>
/// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
/// if there are subscribers to that event.
@@ -115,7 +99,7 @@ namespace Unity.Netcode
// would be stored in different fields
T previousValue = m_InternalValue;
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
if (keepDirtyDelta)
{
@@ -128,7 +112,7 @@ namespace Unity.Netcode
/// <inheritdoc />
public override void ReadField(FastBufferReader reader)
{
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
}
/// <inheritdoc />

View File

@@ -1,4 +1,5 @@
using System;
using UnityEngine;
namespace Unity.Netcode
{
@@ -79,8 +80,15 @@ namespace Unity.Netcode
public virtual void SetDirty(bool isDirty)
{
m_IsDirty = isDirty;
if (m_IsDirty && m_NetworkBehaviour != null)
if (m_IsDirty)
{
if (m_NetworkBehaviour == null)
{
Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " +
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
return;
}
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
@@ -18,7 +17,7 @@ namespace Unity.Netcode
// 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);
public void Read(FastBufferReader reader, ref T value);
}
/// <summary>
@@ -35,79 +34,80 @@ namespace Unity.Netcode
{
writer.WriteUnmanagedSafe(value);
}
public void Read(FastBufferReader reader, out T value)
public void Read(FastBufferReader reader, ref 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.
/// Serializer for FixedStrings
/// </summary>
/// <typeparam name="T"></typeparam>
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
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)
public void Write(FastBufferWriter writer, ref T value)
{
int length = GetLength(ref value);
byte* data = GetUnsafePtr(ref value);
writer.WriteUnmanagedSafe(length);
writer.WriteBytesSafe(data, length);
writer.WriteValueSafe(value);
}
public unsafe void Read(FastBufferReader reader, out T value)
public void Read(FastBufferReader reader, ref T value)
{
value = new T();
reader.ReadValueSafe(out int length);
SetLength(ref value, length);
reader.ReadBytesSafe(GetUnsafePtr(ref value), length);
reader.ReadValueSafeInPlace(ref value);
}
}
/// <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.
/// Serializer for unmanaged INetworkSerializable types
/// </summary>
/// <typeparam name="T"></typeparam>
internal class NetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
internal class UnmanagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INetworkSerializable
{
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);
value.NetworkSerialize(bufferSerializer);
}
public void Read(FastBufferReader reader, out T value)
public void Read(FastBufferReader reader, ref T value)
{
value = new T();
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
ReadValue(ref value, bufferSerializer);
value.NetworkSerialize(bufferSerializer);
}
}
/// <summary>
/// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it
/// has to be null-aware
/// <typeparam name="T"></typeparam>
internal class ManagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : class, INetworkSerializable, new()
{
public void Write(FastBufferWriter writer, ref T value)
{
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
bool isNull = (value == null);
bufferSerializer.SerializeValue(ref isNull);
if (!isNull)
{
value.NetworkSerialize(bufferSerializer);
}
}
public void Read(FastBufferReader reader, ref T value)
{
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
bool isNull = false;
bufferSerializer.SerializeValue(ref isNull);
if (isNull)
{
value = null;
}
else
{
if (value == null)
{
value = new T();
}
value.NetworkSerialize(bufferSerializer);
}
}
}
@@ -164,7 +164,7 @@ namespace Unity.Netcode
}
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
}
public void Read(FastBufferReader reader, out T value)
public void Read(FastBufferReader reader, ref T value)
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
{
@@ -174,34 +174,95 @@ namespace Unity.Netcode
}
}
internal static class NetworkVariableSerializationTypes
/// <summary>
/// This class contains initialization functions for various different types used in NetworkVariables.
/// Generally speaking, these methods are called by a module initializer created by codegen (NetworkBehaviourILPP)
/// and do not need to be called manually.
///
/// There are two types of initializers: Serializers and EqualityCheckers. Every type must have an EqualityChecker
/// registered to it in order to be used in NetworkVariable; however, not all types need a Serializer. Types without
/// a serializer registered will fall back to using the delegates in <see cref="UserNetworkVariableSerialization{T}"/>.
/// If no such delegate has been registered, a type without a serializer will throw an exception on the first attempt
/// to serialize or deserialize it. (Again, however, codegen handles this automatically and this registration doesn't
/// typically need to be performed manually.)
/// </summary>
public static class NetworkVariableSerializationTypes
{
internal static readonly HashSet<Type> BaseSupportedTypes = new HashSet<Type>
/// <summary>
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_UnmanagedByMemcpy<T>() where T : unmanaged
{
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)
};
NetworkVariableSerialization<T>.Serializer = new UnmanagedTypeSerializer<T>();
}
/// <summary>
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
/// NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_UnmanagedINetworkSerializable<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariableSerialization<T>.Serializer = new UnmanagedNetworkSerializableSerializer<T>();
}
/// <summary>
/// Registers a managed type that implements INetworkSerializable and will be serialized through a call to
/// NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_ManagedINetworkSerializable<T>() where T : class, INetworkSerializable, new()
{
NetworkVariableSerialization<T>.Serializer = new ManagedNetworkSerializableSerializer<T>();
}
/// <summary>
/// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString
/// serializers
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_FixedString<T>() where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
NetworkVariableSerialization<T>.Serializer = new FixedStringSerializer<T>();
}
/// <summary>
/// Registers a managed type that will be checked for equality using T.Equals()
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_ManagedIEquatable<T>() where T : class, IEquatable<T>
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsObject;
}
/// <summary>
/// Registers an unmanaged type that will be checked for equality using T.Equals()
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_UnmanagedIEquatable<T>() where T : unmanaged, IEquatable<T>
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEquals;
}
/// <summary>
/// Registers an unmanaged type that will be checked for equality using memcmp and only considered
/// equal if they are bitwise equivalent in memory
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_UnmanagedValueEquals<T>() where T : unmanaged
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ValueEquals;
}
/// <summary>
/// Registers a managed type that will be checked for equality using the == operator
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_ManagedClassEquals<T>() where T : class
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ClassEquals;
}
}
/// <summary>
@@ -212,56 +273,59 @@ namespace Unity.Netcode
/// </summary>
/// <typeparam name="T">The type the associated NetworkVariable is templated on</typeparam>
[Serializable]
public static class NetworkVariableSerialization<T> where T : unmanaged
public static class NetworkVariableSerialization<T>
{
private static INetworkVariableSerializer<T> s_Serializer = GetSerializer();
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
private static INetworkVariableSerializer<T> GetSerializer()
internal delegate bool EqualsDelegate(ref T a, ref T b);
internal static EqualsDelegate AreEqual;
// Compares two values of the same unmanaged type by underlying memory
// Ignoring any overridden value checks
// Size is fixed
internal static unsafe bool ValueEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged
{
if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T)))
// get unmanaged pointers
var aptr = UnsafeUtility.AddressOf(ref a);
var bptr = UnsafeUtility.AddressOf(ref b);
// compare addresses
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0;
}
internal static bool EqualityEqualsObject<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable<TValueType>
{
if (a == null)
{
return new UnmanagedTypeSerializer<T>();
}
if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T)))
{
return new UnmanagedTypeSerializer<T>();
}
if (typeof(Enum).IsAssignableFrom(typeof(T)))
{
return new UnmanagedTypeSerializer<T>();
return b == null;
}
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T)))
if (b == null)
{
// 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 };
return false;
}
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 a.Equals(b);
}
return new FallbackSerializer<T>();
internal static bool EqualityEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable<TValueType>
{
return a.Equals(b);
}
internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class
{
return a == b;
}
internal static void Write(FastBufferWriter writer, ref T value)
{
s_Serializer.Write(writer, ref value);
Serializer.Write(writer, ref value);
}
internal static void Read(FastBufferReader reader, out T value)
internal static void Read(FastBufferReader reader, ref T value)
{
s_Serializer.Read(reader, out value);
Serializer.Read(reader, ref value);
}
}
}

View File

@@ -1,4 +1,3 @@
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
@@ -10,25 +9,8 @@ namespace Unity.Netcode
/// </summary>
internal interface ISceneManagerHandler
{
// Generic action to call when a scene is finished loading/unloading
struct SceneEventAction
{
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);
}
}
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress);
}
}

View File

@@ -338,17 +338,17 @@ namespace Unity.Netcode
/// </summary>
private class DefaultSceneManagerHandler : ISceneManagerHandler
{
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
{
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
sceneEventProgress.SetAsyncOperation(operation);
return operation;
}
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
{
var operation = SceneManager.UnloadSceneAsync(scene);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
sceneEventProgress.SetAsyncOperation(operation);
return operation;
}
}
@@ -887,12 +887,14 @@ namespace Unity.Netcode
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
{
var sceneEventData = BeginSceneEvent();
var clientsThatCompleted = sceneEventProgress.GetClientsWithStatus(true);
var clientsThatTimedOut = sceneEventProgress.GetClientsWithStatus(false);
sceneEventData.SceneEventProgressId = sceneEventProgress.Guid;
sceneEventData.SceneHash = sceneEventProgress.SceneHash;
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
sceneEventData.ClientsCompleted = clientsThatCompleted;
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList();
sceneEventData.ClientsTimedOut = clientsThatTimedOut;
var message = new SceneEventMessage
{
@@ -913,8 +915,8 @@ namespace Unity.Netcode
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
ClientId = NetworkManager.ServerClientId,
LoadSceneMode = sceneEventProgress.LoadSceneMode,
ClientsThatCompleted = sceneEventProgress.DoneClients,
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(),
ClientsThatCompleted = clientsThatCompleted,
ClientsThatTimedOut = clientsThatTimedOut,
});
if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted)
@@ -969,18 +971,9 @@ 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);
// If integration testing, IntegrationTestSceneHandler returns null
if (sceneUnload == null)
{
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
}
else
{
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
}
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress);
// Notify local server that a scene is going to be unloaded
OnSceneEvent?.Invoke(new SceneEvent()
@@ -1024,9 +1017,10 @@ namespace Unity.Netcode
$"because the client scene handle {sceneHandle} was not found in ScenesLoaded!");
}
m_IsSceneEventActive = true;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle],
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress);
ScenesLoaded.Remove(sceneHandle);
@@ -1070,7 +1064,7 @@ namespace Unity.Netcode
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
}
}
@@ -1119,8 +1113,10 @@ namespace Unity.Netcode
// Validate the scene as well as ignore the DDOL (which will have a negative buildIndex)
if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0)
{
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
sceneEventProgress.SceneEventId = sceneEventId;
sceneEventProgress.OnSceneEventCompleted = EmptySceneUnloadedOperation;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress);
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
}
}
@@ -1180,18 +1176,9 @@ namespace Unity.Netcode
}
// Now start loading the scene
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);
}
sceneEventProgress.SceneEventId = sceneEventId;
sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
// Notify the local server that a scene loading event has begun
OnSceneEvent?.Invoke(new SceneEvent()
{
@@ -1355,9 +1342,10 @@ namespace Unity.Netcode
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
}
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded });
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
sceneEventProgress.SceneEventId = sceneEventId;
sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress);
OnSceneEvent?.Invoke(new SceneEvent()
{
@@ -1453,6 +1441,9 @@ namespace Unity.Netcode
}
}
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
sceneEventData.AddDespawnedInSceneNetworkObjects();
// Set the server's scene's handle so the client can build a look up table
sceneEventData.SceneHandle = scene.handle;
@@ -1488,7 +1479,7 @@ namespace Unity.Netcode
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
}
EndSceneEvent(sceneEventId);
}
@@ -1662,8 +1653,10 @@ namespace Unity.Netcode
if (!shouldPassThrough)
{
// If not, then load the scene
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization });
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
sceneEventProgress.SceneEventId = sceneEventId;
sceneEventProgress.OnSceneEventCompleted = ClientLoadedSynchronization;
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
// Notify local client that a scene load has begun
OnSceneEvent?.Invoke(new SceneEvent()
@@ -1880,7 +1873,7 @@ namespace Unity.Netcode
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
}
EndSceneEvent(sceneEventId);
break;
@@ -1889,7 +1882,7 @@ namespace Unity.Netcode
{
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
}
// Notify the local server that the client has finished unloading a scene
OnSceneEvent?.Invoke(new SceneEvent()

View File

@@ -243,13 +243,33 @@ namespace Unity.Netcode
m_NetworkObjectsSync.Add(sobj);
}
}
// Sort by parents before children
m_NetworkObjectsSync.Sort(SortParentedNetworkObjects);
// Sort by INetworkPrefabInstanceHandler implementation before the
// NetworkObjects spawned by the implementation
m_NetworkObjectsSync.Sort(SortNetworkObjects);
// This is useful to know what NetworkObjects a client is going to be synchronized with
// as well as the order in which they will be deserialized
if (m_NetworkManager.LogLevel == LogLevel.Developer)
{
var messageBuilder = new System.Text.StringBuilder(0xFFFF);
messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:");
foreach (var networkObject in m_NetworkObjectsSync)
{
messageBuilder.Append($"{networkObject.name}");
}
NetworkLog.LogInfo(messageBuilder.ToString());
}
}
internal void AddDespawnedInSceneNetworkObjects()
{
m_DespawnedInSceneObjectsSync.Clear();
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager);
// Find all active and non-active in-scene placed NetworkObjects
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
foreach (var sobj in inSceneNetworkObjects)
{
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
@@ -323,6 +343,32 @@ namespace Unity.Netcode
return 0;
}
/// <summary>
/// Sorts the synchronization order of the NetworkObjects to be serialized
/// by parents before children.
/// </summary>
/// <remarks>
/// This only handles late joining players. Spawning and nesting several children
/// dynamically is still handled by the orphaned child list when deserialized out of
/// hierarchical order (i.e. Spawn parent and child dynamically, parent message is
/// dropped and re-sent but child object is received and processed)
/// </remarks>
private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second)
{
// If the first has a parent, move the first down
if (first.transform.parent != null)
{
return 1;
}
else // If the second has a parent and the first does not, then move the first up
if (second.transform.parent != null)
{
return -1;
}
return 0;
}
/// <summary>
/// Client and Server Side:
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)
@@ -398,9 +444,9 @@ namespace Unity.Netcode
int totalBytes = 0;
// Write the number of NetworkObjects we are serializing
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count());
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count);
// Serialize all NetworkObjects that are spawned
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
for (var i = 0; i < m_NetworkObjectsSync.Count; ++i)
{
var noStart = writer.Position;
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
@@ -411,12 +457,11 @@ namespace Unity.Netcode
}
// Write the number of despawned in-scene placed NetworkObjects
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count());
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
// Write the scene handle and GlobalObjectIdHash value
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i)
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;
@@ -462,6 +507,15 @@ namespace Unity.Netcode
}
}
// 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)
{
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
}
var tailPosition = writer.Position;
// Reposition to our count position to the head before we wrote our object count
writer.Seek(headPosition);
@@ -579,6 +633,8 @@ namespace Unity.Netcode
sceneObject.Deserialize(InternalBuffer);
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
}
finally
{
@@ -701,6 +757,84 @@ namespace Unity.Netcode
}
}
/// <summary>
/// For synchronizing any despawned in-scene placed NetworkObjects that were
/// despawned by the server during synchronization or scene loading
/// </summary>
private void DeserializeDespawnedInScenePlacedNetworkObjects()
{
// 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];
// Find all active and non-active in-scene placed NetworkObjects
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
foreach (var inSceneObject in inSceneNetworkObjects)
{
if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash))
{
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!");
}
}
}
/// <summary>
/// Client Side:
/// During the processing of a server sent Event_Sync, this method will be called for each scene once
@@ -734,72 +868,9 @@ namespace Unity.Netcode
}
}
// 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>>();
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
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
{

View File

@@ -58,12 +58,13 @@ namespace Unity.Netcode
/// <summary>
/// List of clientIds of those clients that is done loading the scene.
/// </summary>
internal List<ulong> DoneClients { get; } = new List<ulong>();
internal Dictionary<ulong, bool> ClientsProcessingSceneEvent { get; } = new Dictionary<ulong, bool>();
internal List<ulong> ClientsThatDisconnected = new List<ulong>();
/// <summary>
/// The local time when the scene event was "roughly started"
/// This is when the current scene event will have timed out
/// </summary>
internal float TimeAtInitiation { get; }
internal float WhenSceneEventHasTimedOut;
/// <summary>
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
@@ -75,17 +76,15 @@ namespace Unity.Netcode
/// </summary>
internal OnCompletedDelegate OnComplete;
/// <summary>
/// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred.
/// </summary>
internal bool IsCompleted { get; private set; }
internal bool TimedOut { get; private set; }
internal Action<uint> OnSceneEventCompleted;
/// <summary>
/// If all clients are done loading the scene, at the moment of completed.
/// This will make sure that we only have timed out if we never completed
/// </summary>
internal bool AreAllClientsDoneLoading { get; private set; }
internal bool HasTimedOut()
{
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup;
}
/// <summary>
/// The hash value generated from the full scene path
@@ -93,9 +92,10 @@ namespace Unity.Netcode
internal uint SceneHash { get; set; }
internal Guid Guid { get; } = Guid.NewGuid();
internal uint SceneEventId;
private Coroutine m_TimeOutCoroutine;
private AsyncOperation m_SceneLoadOperation;
private AsyncOperation m_AsyncOperation;
private NetworkManager m_NetworkManager { get; }
@@ -105,21 +105,62 @@ namespace Unity.Netcode
internal LoadSceneMode LoadSceneMode;
internal List<ulong> ClientsThatStartedSceneEvent;
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
{
var clients = new List<ulong>();
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if (clientStatus.Value == completedSceneEvent)
{
clients.Add(clientStatus.Key);
}
}
// If we are getting the list of clients that have not completed the
// scene event, then add any clients that disconnected during this
// scene event.
if (!completedSceneEvent)
{
clients.AddRange(ClientsThatDisconnected);
}
return clients;
}
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 = Time.realtimeSinceStartup;
if (networkManager.IsServer)
{
m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
// Track the clients that were connected when we started this event
foreach (var connectedClientId in networkManager.ConnectedClientsIds)
{
ClientsProcessingSceneEvent.Add(connectedClientId, false);
}
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
}
}
Status = status;
}
/// <summary>
/// Remove the client from the clients processing the current scene event
/// Add this client to the clients that disconnected list
/// </summary>
private void OnClientDisconnectCallback(ulong clientId)
{
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsThatDisconnected.Add(clientId);
ClientsProcessingSceneEvent.Remove(clientId);
}
}
/// <summary>
/// Coroutine that checks to see if the scene event is complete every network tick period.
/// This will handle completing the scene event when one or more client(s) disconnect(s)
@@ -129,79 +170,104 @@ namespace Unity.Netcode
internal IEnumerator TimeOutSceneEventProgress()
{
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
while (!TimedOut && !IsCompleted)
while (!HasTimedOut())
{
yield return waitForNetworkTick;
CheckCompletion();
if (!IsCompleted)
{
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
}
TryFinishingSceneEventProgress();
}
}
internal void AddClientAsDone(ulong clientId)
{
DoneClients.Add(clientId);
CheckCompletion();
}
internal void RemoveClientAsDone(ulong clientId)
{
DoneClients.Remove(clientId);
CheckCompletion();
}
internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation)
{
m_SceneLoadOperation = sceneLoadOperation;
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)
/// Sets the client's scene event progress to finished/true
/// </summary>
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
internal void ClientFinishedSceneEvent(ulong clientId)
{
sceneEventAction.Completed = SetComplete;
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsProcessingSceneEvent[clientId] = true;
TryFinishingSceneEventProgress();
}
}
/// <summary>
/// Finalizes the SceneEventProgress
/// Determines if the scene event has finished for both
/// client(s) and server.
/// </summary>
internal void SetComplete()
/// <remarks>
/// The server checks if all known clients processing this scene event
/// have finished and then it returns its local AsyncOperation status.
/// Clients finish when their AsyncOperation finishes.
/// </remarks>
private bool HasFinished()
{
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 the network session is terminated/terminating then finish tracking
// this scene event
if (!IsNetworkSessionActive())
{
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
return true;
}
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
}
internal void CheckCompletion()
{
try
// Clients skip over this
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
if (!clientStatus.Value)
{
SetComplete();
return false;
}
}
catch (Exception ex)
// Return the local scene event's AsyncOperation status
return m_AsyncOperation.isDone;
}
/// <summary>
/// Sets the AsyncOperation for the scene load/unload event
/// </summary>
internal void SetAsyncOperation(AsyncOperation asyncOperation)
{
m_AsyncOperation = asyncOperation;
m_AsyncOperation.completed += new Action<AsyncOperation>(asyncOp2 =>
{
Debug.LogException(ex);
// Don't invoke the callback if the network session is disconnected
// during a SceneEventProgress
if (IsNetworkSessionActive())
{
OnSceneEventCompleted?.Invoke(SceneEventId);
}
// Go ahead and try finishing even if the network session is terminated/terminating
// as we might need to stop the coroutine
TryFinishingSceneEventProgress();
});
}
internal bool IsNetworkSessionActive()
{
return m_NetworkManager != null && m_NetworkManager.IsListening && !m_NetworkManager.ShutdownInProgress;
}
/// <summary>
/// Will try to finish the current scene event in progress as long as
/// all conditions are met.
/// </summary>
internal void TryFinishingSceneEventProgress()
{
if (HasFinished() || HasTimedOut())
{
// Don't attempt to finalize this scene event if we are no longer listening or a shutdown is in progress
if (IsNetworkSessionActive())
{
OnComplete?.Invoke(this);
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
}
if (m_TimeOutCoroutine != null)
{
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
}
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Unity.Netcode
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
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.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.ReadNetworkSerializableInPlace(ref 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)

View File

@@ -461,6 +461,19 @@ namespace Unity.Netcode
}
}
/// <summary>
/// Read an INetworkSerializable in-place, without constructing a new one
/// Note that this will NOT check for null before calling NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value">INetworkSerializable instance</param>
/// <exception cref="NotImplementedException"></exception>
public void ReadNetworkSerializableInPlace<T>(ref T value) where T : INetworkSerializable
{
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(this));
value.NetworkSerialize(bufferSerializer);
}
/// <summary>
/// Reads a string
/// NOTE: ALLOCATES
@@ -1310,5 +1323,24 @@ namespace Unity.Netcode
value.Length = length;
ReadBytesSafe(value.GetUnsafePtr(), length);
}
/// <summary>
/// Read a FixedString value.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValueSafeInPlace<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
ReadUnmanagedSafe(out int length);
value.Length = length;
ReadBytesSafe(value.GetUnsafePtr(), length);
}
}
}

View File

@@ -139,7 +139,16 @@ namespace Unity.Netcode
/// </summary>
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
/// <returns>This returns the <see cref="GameObject"/> that the <see cref="NetworkObject"/> is attached to and is referenced by the <see cref="NetworkObjectReference"/> passed in as a parameter</returns>
public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject;
public static implicit operator GameObject(NetworkObjectReference networkObjectRef)
{
var networkObject = Resolve(networkObjectRef);
if (networkObject != null)
{
return networkObject.gameObject;
}
return null;
}
/// <summary>
/// Implicitly convert <see cref="GameObject"/> to <see cref="NetworkObject"/>.

View File

@@ -119,8 +119,7 @@ namespace Unity.Netcode
// Now we register all
foreach (var gameObject in networkPrefabOverrides)
{
var targetNetworkObject = gameObject.GetComponent<NetworkObject>();
if (targetNetworkObject != null)
if (gameObject.TryGetComponent<NetworkObject>(out var targetNetworkObject))
{
if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash))
{

View File

@@ -213,11 +213,11 @@ namespace Unity.Netcode
return;
}
networkObject.OwnerClientId = NetworkManager.ServerClientId;
// Server removes the entry and takes over ownership before notifying
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
networkObject.OwnerClientId = NetworkManager.ServerClientId;
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
@@ -278,11 +278,14 @@ namespace Unity.Netcode
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
if (networkObject.IsNetworkVisibleTo(client.Value.ClientId))
{
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId);
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
}
@@ -314,48 +317,33 @@ namespace Unity.Netcode
}
/// <summary>
/// Should only run on the client
/// Creates a local NetowrkObject to be spawned.
/// </summary>
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, int? networkSceneHandle, Vector3? position, Quaternion? rotation, bool isReparented = false)
/// <remarks>
/// For most cases this is client-side only, with the exception of when the server
/// is spawning a player.
/// </remarks>
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject)
{
NetworkObject parentNetworkObject = null;
NetworkObject networkObject = null;
var globalObjectIdHash = sceneObject.Header.Hash;
var position = sceneObject.Header.HasTransform ? sceneObject.Transform.Position : default;
var rotation = sceneObject.Header.HasTransform ? sceneObject.Transform.Rotation : default;
var scale = sceneObject.Header.HasTransform ? sceneObject.Transform.Scale : default;
var parentNetworkId = sceneObject.Header.HasParent ? sceneObject.ParentObjectId : default;
var worldPositionStays = sceneObject.Header.HasParent ? sceneObject.WorldPositionStays : true;
var isSpawnedByPrefabHandler = false;
if (parentNetworkId != null && !isReparented)
{
if (SpawnedObjects.TryGetValue(parentNetworkId.Value, out NetworkObject networkObject))
{
parentNetworkObject = networkObject;
}
else
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child");
}
}
}
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
// If scene management is disabled or the NetworkObject was dynamically spawned
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
{
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
{
// Let the handler spawn the NetworkObject
var networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerClientId, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity));
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.Header.OwnerClientId, position, rotation);
networkObject.NetworkManagerOwner = NetworkManager;
if (parentNetworkObject != null)
{
networkObject.transform.SetParent(parentNetworkObject.transform, true);
}
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
return networkObject;
isSpawnedByPrefabHandler = true;
}
else
{
@@ -383,31 +371,18 @@ namespace Unity.Netcode
{
NetworkLog.LogError($"Failed to create object locally. [{nameof(globalObjectIdHash)}={globalObjectIdHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?");
}
return null;
}
// Otherwise, instantiate an instance of the NetworkPrefab linked to the prefabHash
var networkObject = ((position == null && rotation == null) ? UnityEngine.Object.Instantiate(networkPrefabReference) : UnityEngine.Object.Instantiate(networkPrefabReference, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent<NetworkObject>();
networkObject.NetworkManagerOwner = NetworkManager;
if (parentNetworkObject != null)
else
{
networkObject.transform.SetParent(parentNetworkObject.transform, true);
// Create prefab instance
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
networkObject.NetworkManagerOwner = NetworkManager;
}
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
return networkObject;
}
}
else
else // Get the in-scene placed NetworkObject
{
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, networkSceneHandle);
networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, sceneObject.NetworkSceneHandle);
if (networkObject == null)
{
@@ -415,17 +390,78 @@ namespace Unity.Netcode
{
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
}
return null;
}
if (parentNetworkObject != null)
// Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so
// NetworkBehaviours will have their OnNetworkSpawn method invoked
if (networkObject != null && !networkObject.gameObject.activeInHierarchy)
{
networkObject.transform.SetParent(parentNetworkObject.transform, true);
networkObject.gameObject.SetActive(true);
}
}
if (networkObject != null)
{
// SPECIAL CASE:
// This is a special case scenario where a late joining client has joined and loaded one or
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
// synchronization information does not indicate the NetworkObject in question has a parent.
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
if (sceneObject.Header.IsSceneObject && !sceneObject.Header.HasParent && networkObject.transform.parent != null)
{
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
// include parenting, then we need to force the removal of that parent
if (networkObject.transform.parent.GetComponent<NetworkObject>() != null)
{
// remove the parent
networkObject.ApplyNetworkParenting(true, true);
}
}
return networkObject;
// Set the transform unless we were spawned by a prefab handler
// Note: prefab handlers are provided the position and rotation
// but it is up to the user to set those values
if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler)
{
if (worldPositionStays)
{
networkObject.transform.position = position;
networkObject.transform.rotation = rotation;
}
else
{
networkObject.transform.localPosition = position;
networkObject.transform.localRotation = rotation;
}
// SPECIAL CASE:
// Since players are created uniquely we don't apply scale because
// the ConnectionApprovalResponse does not currently provide the
// ability to specify scale. So, we just use the default scale of
// the network prefab used to represent the player.
// Note: not doing this would set the player's scale to zero since
// that is the default value of Vector3.
if (!sceneObject.Header.IsPlayerObject)
{
networkObject.transform.localScale = scale;
}
}
if (sceneObject.Header.HasParent)
{
// Go ahead and set network parenting properties
networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays);
}
// Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL
// until the scene is loaded. They are then migrated back into the newly loaded and currently active scene.
if (!sceneObject.Header.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
return networkObject;
}
// Ran on both server and client
@@ -545,7 +581,6 @@ namespace Unity.Netcode
}
}
networkObject.SetCachedParent(networkObject.transform.parent);
networkObject.ApplyNetworkParenting();
NetworkObject.CheckOrphanChildren();
@@ -575,8 +610,6 @@ namespace Unity.Netcode
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty(true);
}
internal ulong? GetSpawnParentId(NetworkObject networkObject)
@@ -745,15 +778,26 @@ namespace Unity.Netcode
}
// If we are shutting down the NetworkManager, then ignore resetting the parent
if (!NetworkManager.ShutdownInProgress)
// and only attempt to remove the child's parent on the server-side
if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer)
{
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
foreach (var spawnedNetObj in SpawnedObjectsList)
{
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
if (isReparented && latestParent == networkObject.NetworkObjectId)
var latestParent = spawnedNetObj.GetNetworkParenting();
if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId)
{
spawnedNetObj.gameObject.transform.parent = null;
// Try to remove the parent using the cached WorldPositioNStays value
// Note: WorldPositionStays will still default to true if this was an
// in-scene placed NetworkObject and parenting was predefined in the
// scene via the editor.
if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays())
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed");
}
}
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{

View File

@@ -25,7 +25,7 @@ namespace Unity.Netcode
public double TickOffset => m_CachedTickOffset;
/// <summary>
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>.
/// </summary>
public double Time => m_TimeSec;
@@ -35,13 +35,13 @@ namespace Unity.Netcode
public float TimeAsFloat => (float)m_TimeSec;
/// <summary>
/// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedTime"/>
/// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedUnscaledTime"/>.
/// </summary>
public double FixedTime => m_CachedTick * m_TickInterval;
/// <summary>
/// Gets the fixed delta time. This value is based on the <see cref="TickRate"/> and stays constant.
/// Similar to <see cref="Time.fixedDeltaTime"/> There is no equivalent to <see cref="Time.deltaTime"/>
/// Similar to <see cref="Time.fixedUnscaledTime"/> There is no equivalent to <see cref="Time.deltaTime"/>.
/// </summary>
public float FixedDeltaTime => (float)m_TickInterval;

View File

@@ -76,7 +76,7 @@ namespace Unity.Netcode
}
/// <summary>
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.deltaTime or similar.
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.unscaledDeltaTime or similar.
/// </summary>
/// <param name="deltaTimeSec">The amount of time to advance. The delta time which passed since Advance was last called.</param>
/// <returns></returns>

View File

@@ -7,6 +7,7 @@ using UnityEngine.Networking;
namespace Unity.Netcode.Transports.UNET
{
[AddComponentMenu("Netcode/UNet Transport")]
public class UNetTransport : NetworkTransport
{
public enum SendMode

View File

@@ -1,5 +1,9 @@
using System;
using Unity.Networking.Transport;
#if UTP_TRANSPORT_2_0_ABOVE
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
#endif
namespace Unity.Netcode.Transports.UTP
{
@@ -25,7 +29,11 @@ namespace Unity.Netcode.Transports.UTP
{
fixed (byte* dataPtr = m_Data)
{
#if UTP_TRANSPORT_2_0_ABOVE
reader.ReadBytesUnsafe(dataPtr, reader.Length);
#else
reader.ReadBytes(dataPtr, reader.Length);
#endif
}
}
@@ -62,7 +70,11 @@ namespace Unity.Netcode.Transports.UTP
{
fixed (byte* dataPtr = m_Data)
{
#if UTP_TRANSPORT_2_0_ABOVE
reader.ReadBytesUnsafe(dataPtr + m_Offset + m_Length, reader.Length);
#else
reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length);
#endif
}
}

View File

@@ -8,22 +8,30 @@ namespace Unity.Netcode.Transports.UTP
/// <summary>Queue for batched messages meant to be sent through UTP.</summary>
/// <remarks>
/// Messages should be pushed on the queue with <see cref="PushMessage"/>. To send batched
/// messages, call <see cref="FillWriter"> with the <see cref="DataStreamWriter"/> obtained from
/// <see cref="NetworkDriver.BeginSend"/>. This will fill the writer with as many messages as
/// possible. If the send is successful, call <see cref="Consume"/> to remove the data from the
/// queue.
/// messages, call <see cref="FillWriterWithMessages"/> or <see cref="FillWriterWithBytes"/>
/// with the <see cref="DataStreamWriter"/> obtained from <see cref="NetworkDriver.BeginSend"/>.
/// This will fill the writer with as many messages/bytes as possible. If the send is
/// successful, call <see cref="Consume"/> to remove the data from the queue.
///
/// This is meant as a companion to <see cref="BatchedReceiveQueue"/>, which should be used to
/// read messages sent with this queue.
/// </remarks>
internal struct BatchedSendQueue : IDisposable
{
private NativeArray<byte> m_Data;
// Note that we're using NativeList basically like a growable NativeArray, where the length
// of the list is the capacity of our array. (We can't use the capacity of the list as our
// queue capacity because NativeList may elect to set it higher than what we'd set it to
// with SetCapacity, which breaks the logic of our code.)
private NativeList<byte> m_Data;
private NativeArray<int> m_HeadTailIndices;
private int m_MaximumCapacity;
private int m_MinimumCapacity;
/// <summary>Overhead that is added to each message in the queue.</summary>
public const int PerMessageOverhead = sizeof(int);
internal const int MinimumMinimumCapacity = 4096;
// Indices into m_HeadTailIndicies.
private const int k_HeadInternalIndex = 0;
private const int k_TailInternalIndex = 1;
@@ -43,18 +51,33 @@ namespace Unity.Netcode.Transports.UTP
}
public int Length => TailIndex - HeadIndex;
public int Capacity => m_Data.Length;
public bool IsEmpty => HeadIndex == TailIndex;
public bool IsCreated => m_Data.IsCreated;
/// <summary>Construct a new empty send queue.</summary>
/// <param name="capacity">Maximum capacity of the send queue.</param>
public BatchedSendQueue(int capacity)
{
m_Data = new NativeArray<byte>(capacity, Allocator.Persistent);
// Make sure the maximum capacity will be even.
m_MaximumCapacity = capacity + (capacity & 1);
// We pick the minimum capacity such that if we keep doubling it, we'll eventually hit
// the maximum capacity exactly. The alternative would be to use capacities that are
// powers of 2, but this can lead to over-allocating quite a bit of memory (especially
// since we expect maximum capacities to be in the megabytes range). The approach taken
// here avoids this issue, at the cost of not having allocations of nice round sizes.
m_MinimumCapacity = m_MaximumCapacity;
while (m_MinimumCapacity / 2 >= MinimumMinimumCapacity)
{
m_MinimumCapacity /= 2;
}
m_Data = new NativeList<byte>(m_MinimumCapacity, Allocator.Persistent);
m_HeadTailIndices = new NativeArray<int>(2, Allocator.Persistent);
m_Data.ResizeUninitialized(m_MinimumCapacity);
HeadIndex = 0;
TailIndex = 0;
}
@@ -68,18 +91,28 @@ namespace Unity.Netcode.Transports.UTP
}
}
/// <summary>Write a raw buffer to a DataStreamWriter.</summary>
private unsafe void WriteBytes(ref DataStreamWriter writer, byte* data, int length)
{
#if UTP_TRANSPORT_2_0_ABOVE
writer.WriteBytesUnsafe(data, length);
#else
writer.WriteBytes(data, length);
#endif
}
/// <summary>Append data at the tail of the queue. No safety checks.</summary>
private void AppendDataAtTail(ArraySegment<byte> data)
{
unsafe
{
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, m_Data.Length - TailIndex);
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex);
writer.WriteInt(data.Count);
fixed (byte* dataPtr = data.Array)
{
writer.WriteBytes(dataPtr + data.Offset, data.Count);
WriteBytes(ref writer, dataPtr + data.Offset, data.Count);
}
}
@@ -100,16 +133,16 @@ namespace Unity.Netcode.Transports.UTP
}
// Check if there's enough room after the current tail index.
if (m_Data.Length - TailIndex >= sizeof(int) + message.Count)
if (Capacity - TailIndex >= sizeof(int) + message.Count)
{
AppendDataAtTail(message);
return true;
}
// Check if there would be enough room if we moved data at the beginning of m_Data.
if (m_Data.Length - TailIndex + HeadIndex >= sizeof(int) + message.Count)
// Move the data at the beginning of of m_Data. Either it will leave enough space for
// the message, or we'll grow m_Data and will want the data at the beginning anyway.
if (HeadIndex > 0 && Length > 0)
{
// Move the data back at the beginning of m_Data.
unsafe
{
UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
@@ -117,12 +150,38 @@ namespace Unity.Netcode.Transports.UTP
TailIndex = Length;
HeadIndex = 0;
}
// If there's enough space left at the end for the message, now is a good time to trim
// the capacity of m_Data if it got very large. We define "very large" here as having
// more than 75% of m_Data unused after adding the new message.
if (Capacity - TailIndex >= sizeof(int) + message.Count)
{
AppendDataAtTail(message);
while (TailIndex < Capacity / 4 && Capacity > m_MinimumCapacity)
{
m_Data.ResizeUninitialized(Capacity / 2);
}
return true;
}
return false;
// If we get here we need to grow m_Data until the data fits (or it's too large).
while (Capacity - TailIndex < sizeof(int) + message.Count)
{
// Can't grow m_Data anymore. Message simply won't fit.
if (Capacity * 2 > m_MaximumCapacity)
{
return false;
}
m_Data.ResizeUninitialized(Capacity * 2);
}
// If we get here we know there's now enough room for the message.
AppendDataAtTail(message);
return true;
}
/// <summary>
@@ -149,12 +208,12 @@ namespace Unity.Netcode.Transports.UTP
unsafe
{
var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
var reader = new DataStreamReader(m_Data.AsArray());
var writerAvailable = writer.Capacity;
var readerOffset = 0;
var readerOffset = HeadIndex;
while (readerOffset < Length)
while (readerOffset < TailIndex)
{
reader.SeekSet(readerOffset);
var messageLength = reader.ReadInt();
@@ -168,7 +227,7 @@ namespace Unity.Netcode.Transports.UTP
writer.WriteInt(messageLength);
var messageOffset = HeadIndex + reader.GetBytesRead();
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
writerAvailable -= sizeof(int) + messageLength;
readerOffset += sizeof(int) + messageLength;
@@ -205,7 +264,7 @@ namespace Unity.Netcode.Transports.UTP
unsafe
{
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
}
return copyLength;
@@ -219,10 +278,14 @@ namespace Unity.Netcode.Transports.UTP
/// <param name="size">Number of bytes to consume from the queue.</param>
public void Consume(int size)
{
// Adjust the head/tail indices such that we consume the given size.
if (size >= Length)
{
HeadIndex = 0;
TailIndex = 0;
// This is a no-op if m_Data is already at minimum capacity.
m_Data.ResizeUninitialized(m_MinimumCapacity);
}
else
{

View File

@@ -4,25 +4,24 @@ using AOT;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Networking.Transport;
using UnityEngine;
namespace Unity.Netcode.Transports.UTP
{
[BurstCompile]
internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage
{
static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
private static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> s_ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
private static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> s_SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
private static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> s_InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer,
int staticInstanceBufferLength,
NetworkSettings settings)
{
return new NetworkPipelineStage(
ReceiveFunction,
SendFunction,
InitializeConnectionFunction,
s_ReceiveFunction,
s_SendFunction,
s_InitializeConnectionFunction,
ReceiveCapacity: 0,
SendCapacity: 0,
HeaderCapacity: 0,

View File

@@ -0,0 +1,188 @@
using System;
using System.IO;
using UnityEngine;
namespace Unity.Netcode.Transports.UTP
{
/// <summary>
/// Component to add to a NetworkManager if you want the certificates to be loaded from files.
/// Mostly helpful to ease development and testing, especially with self-signed certificates
///
/// Shipping code should make the calls to
/// - SetServerSecrets
/// - SetClientSecrets
/// directly, instead of relying on this.
/// </summary>
public class SecretsLoaderHelper : MonoBehaviour
{
internal struct ServerSecrets
{
public string ServerPrivate;
public string ServerCertificate;
};
internal struct ClientSecrets
{
public string ServerCommonName;
public string ClientCertificate;
};
private void Awake()
{
var serverSecrets = new ServerSecrets();
try
{
serverSecrets.ServerCertificate = ServerCertificate;
}
catch (Exception exception)
{
Debug.Log(exception);
}
try
{
serverSecrets.ServerPrivate = ServerPrivate;
}
catch (Exception exception)
{
Debug.Log(exception);
}
var clientSecrets = new ClientSecrets();
try
{
clientSecrets.ClientCertificate = ClientCA;
}
catch (Exception exception)
{
Debug.Log(exception);
}
try
{
clientSecrets.ServerCommonName = ServerCommonName;
}
catch (Exception exception)
{
Debug.Log(exception);
}
var unityTransportComponent = GetComponent<UnityTransport>();
if (unityTransportComponent == null)
{
Debug.LogError($"You need to select the UnityTransport protocol, in the NetworkManager, in order for the SecretsLoaderHelper component to be useful.");
return;
}
unityTransportComponent.SetServerSecrets(serverSecrets.ServerCertificate, serverSecrets.ServerPrivate);
unityTransportComponent.SetClientSecrets(clientSecrets.ServerCommonName, clientSecrets.ClientCertificate);
}
[Tooltip("Hostname")]
[SerializeField]
private string m_ServerCommonName = "localhost";
/// <summary>Common name of the server (typically its hostname).</summary>
public string ServerCommonName
{
get => m_ServerCommonName;
set => m_ServerCommonName = value;
}
[Tooltip("Client CA filepath. Useful with self-signed certificates")]
[SerializeField]
private string m_ClientCAFilePath = ""; // "Assets/Secure/myGameClientCA.pem"
/// <summary>Client CA filepath. Useful with self-signed certificates</summary>
public string ClientCAFilePath
{
get => m_ClientCAFilePath;
set => m_ClientCAFilePath = value;
}
[Tooltip("Client CA Override. Only useful for development with self-signed certificates. Certificate content, for platforms that lack file access (WebGL)")]
[SerializeField]
private string m_ClientCAOverride = "";
/// <summary>
/// Client CA Override. Only useful for development with self-signed certificates.
/// Certificate content, for platforms that lack file access (WebGL)
/// </summary>
public string ClientCAOverride
{
get => m_ClientCAOverride;
set => m_ClientCAOverride = value;
}
[Tooltip("Server Certificate filepath")]
[SerializeField]
private string m_ServerCertificateFilePath = ""; // "Assets/Secure/myGameServerCertificate.pem"
/// <summary>Server Certificate filepath</summary>
public string ServerCertificateFilePath
{
get => m_ServerCertificateFilePath;
set => m_ServerCertificateFilePath = value;
}
[Tooltip("Server Private Key filepath")]
[SerializeField]
private string m_ServerPrivateFilePath = ""; // "Assets/Secure/myGameServerPrivate.pem"
/// <summary>Server Private Key filepath</summary>
public string ServerPrivateFilePath
{
get => m_ServerPrivateFilePath;
set => m_ServerPrivate = value;
}
private string m_ClientCA;
/// <summary>CA certificate used by the client.</summary>
public string ClientCA
{
get
{
if (m_ClientCAOverride != "")
{
return m_ClientCAOverride;
}
return ReadFile(m_ClientCAFilePath, "Client Certificate");
}
set => m_ClientCA = value;
}
private string m_ServerCertificate;
/// <summary>Certificate used by the server.</summary>
public string ServerCertificate
{
get => ReadFile(m_ServerCertificateFilePath, "Server Certificate");
set => m_ServerCertificate = value;
}
private string m_ServerPrivate;
/// <summary>Private key used by the server.</summary>
public string ServerPrivate
{
get => ReadFile(m_ServerPrivateFilePath, "Server Key");
set => m_ServerPrivate = value;
}
private static string ReadFile(string path, string label)
{
if (path == null || path == "")
{
return "";
}
var reader = new StreamReader(path);
string fileContent = reader.ReadToEnd();
Debug.Log((fileContent.Length > 1) ? ("Successfully loaded " + fileContent.Length + " byte(s) from " + label) : ("Could not read " + label + " file"));
return fileContent;
}
}
}

View File

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

View File

@@ -1,3 +1,10 @@
// NetSim Implementation compilation boilerplate
// All references to UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED should be defined in the same way,
// as any discrepancies are likely to result in build failures
#if UNITY_EDITOR || (DEVELOPMENT_BUILD && !UNITY_MP_TOOLS_NETSIM_DISABLED_IN_DEVELOP) || (!DEVELOPMENT_BUILD && UNITY_MP_TOOLS_NETSIM_ENABLED_IN_RELEASE)
#define UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED
#endif
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -8,6 +15,13 @@ using Unity.Collections;
using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay;
using Unity.Networking.Transport.Utilities;
#if UTP_TRANSPORT_2_0_ABOVE
using Unity.Networking.Transport.TLS;
#endif
#if !UTP_TRANSPORT_2_0_ABOVE
using NetworkEndpoint = Unity.Networking.Transport.NetworkEndPoint;
#endif
namespace Unity.Netcode.Transports.UTP
{
@@ -88,6 +102,7 @@ namespace Unity.Netcode.Transports.UTP
/// The Netcode for GameObjects NetworkTransport for UnityTransport.
/// Note: This is highly recommended to use over UNet.
/// </summary>
[AddComponentMenu("Netcode/Unity Transport")]
public partial class UnityTransport : NetworkTransport, INetworkStreamDriverConstructor
{
/// <summary>
@@ -125,8 +140,13 @@ namespace Unity.Netcode.Transports.UTP
/// <summary>
/// The default maximum send queue size
/// </summary>
[Obsolete("MaxSendQueueSize is now determined dynamically (can still be set programmatically using the MaxSendQueueSize property). This initial value is not used anymore.", false)]
public const int InitialMaxSendQueueSize = 16 * InitialMaxPayloadSize;
// Maximum reliable throughput, assuming the full reliable window can be sent on every
// frame at 60 FPS. This will be a large over-estimation in any realistic scenario.
private const int k_MaxReliableThroughput = (NetworkParameterConstants.MTU * 32 * 60) / 1000; // bytes per millisecond
private static ConnectionAddressData s_DefaultConnectionAddressData = new ConnectionAddressData { Address = "127.0.0.1", Port = 7777, ServerListenAddress = string.Empty };
#pragma warning disable IDE1006 // Naming Styles
@@ -146,6 +166,30 @@ namespace Unity.Netcode.Transports.UTP
[SerializeField]
private ProtocolType m_ProtocolType;
#if UTP_TRANSPORT_2_0_ABOVE
[Tooltip("Per default the client/server will communicate over UDP. Set to true to communicate with WebSocket.")]
[SerializeField]
private bool m_UseWebSockets = false;
public bool UseWebSockets
{
get => m_UseWebSockets;
set => m_UseWebSockets = value;
}
/// <summary>
/// Per default the client/server communication will not be encrypted. Select true to enable DTLS for UDP and TLS for Websocket.
/// </summary>
[Tooltip("Per default the client/server communication will not be encrypted. Select true to enable DTLS for UDP and TLS for Websocket.")]
[SerializeField]
private bool m_UseEncryption = false;
public bool UseEncryption
{
get => m_UseEncryption;
set => m_UseEncryption = value;
}
#endif
[Tooltip("The maximum amount of packets that can be in the internal send/receive queues. Basically this is how many packets can be sent/received in a single update/frame.")]
[SerializeField]
private int m_MaxPacketQueueSize = InitialMaxPacketQueueSize;
@@ -169,15 +213,17 @@ namespace Unity.Netcode.Transports.UTP
set => m_MaxPayloadSize = value;
}
[Tooltip("The maximum size in bytes of the transport send queue. The send queue accumulates messages for batching and stores messages when other internal send queues are full. If you routinely observe an error about too many in-flight packets, try increasing this.")]
[SerializeField]
private int m_MaxSendQueueSize = InitialMaxSendQueueSize;
private int m_MaxSendQueueSize = 0;
/// <summary>The maximum size in bytes of the transport send queue.</summary>
/// <remarks>
/// The send queue accumulates messages for batching and stores messages when other internal
/// send queues are full. If you routinely observe an error about too many in-flight packets,
/// try increasing this.
/// send queues are full. Note that there should not be any need to set this value manually
/// since the send queue size is dynamically sized based on need.
///
/// This value should only be set if you have particular requirements (e.g. if you want to
/// limit the memory usage of the send queues). Note however that setting this value too low
/// can easily lead to disconnections under heavy traffic.
/// </remarks>
public int MaxSendQueueSize
{
@@ -263,12 +309,14 @@ namespace Unity.Netcode.Transports.UTP
[SerializeField]
public string ServerListenAddress;
private static NetworkEndPoint ParseNetworkEndpoint(string ip, ushort port)
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port)
{
if (!NetworkEndPoint.TryParse(ip, port, out var endpoint))
NetworkEndpoint endpoint = default;
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4) &&
!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6))
{
Debug.LogError($"Invalid network endpoint: {ip}:{port}.");
return default;
}
return endpoint;
@@ -277,12 +325,12 @@ namespace Unity.Netcode.Transports.UTP
/// <summary>
/// Endpoint (IP address and port) clients will connect to.
/// </summary>
public NetworkEndPoint ServerEndPoint => ParseNetworkEndpoint(Address, Port);
public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port);
/// <summary>
/// Endpoint (IP address and port) server will listen/bind on.
/// </summary>
public NetworkEndPoint ListenEndPoint => ParseNetworkEndpoint((ServerListenAddress == string.Empty) ? Address : ServerListenAddress, Port);
public NetworkEndpoint ListenEndPoint => ParseNetworkEndpoint((ServerListenAddress?.Length == 0) ? Address : ServerListenAddress, Port);
}
/// <summary>
@@ -326,6 +374,9 @@ namespace Unity.Netcode.Transports.UTP
/// - packet jitter (variances in latency, see: https://en.wikipedia.org/wiki/Jitter)
/// - packet drop rate (packet loss)
/// </summary>
#if UTP_TRANSPORT_2_0_ABOVE
[Obsolete("DebugSimulator is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)]
#endif
public SimulatorParameters DebugSimulator = new SimulatorParameters
{
PacketDelayMS = 0,
@@ -333,6 +384,8 @@ namespace Unity.Netcode.Transports.UTP
PacketDropRate = 0
};
internal uint? DebugSimulatorRandomSeed { get; set; } = null;
private struct PacketLossCache
{
public int PacketsReceived;
@@ -340,6 +393,10 @@ namespace Unity.Netcode.Transports.UTP
public float PacketLoss;
};
internal static event Action<int, NetworkDriver> TransportInitialized;
internal static event Action<int> TransportDisposed;
internal NetworkDriver NetworkDriver => m_Driver;
private PacketLossCache m_PacketLossCache = new PacketLossCache();
private State m_State = State.Disconnected;
@@ -383,6 +440,8 @@ namespace Unity.Netcode.Transports.UTP
out m_UnreliableFragmentedPipeline,
out m_UnreliableSequencedFragmentedPipeline,
out m_ReliableSequencedPipeline);
TransportInitialized?.Invoke(GetInstanceID(), NetworkDriver);
}
private void DisposeInternals()
@@ -400,6 +459,8 @@ namespace Unity.Netcode.Transports.UTP
}
m_SendQueue.Clear();
TransportDisposed?.Invoke(GetInstanceID());
}
private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery)
@@ -425,7 +486,7 @@ namespace Unity.Netcode.Transports.UTP
private bool ClientBindAndConnect()
{
var serverEndpoint = default(NetworkEndPoint);
var serverEndpoint = default(NetworkEndpoint);
if (m_ProtocolType == ProtocolType.RelayUnityTransport)
{
@@ -438,6 +499,7 @@ namespace Unity.Netcode.Transports.UTP
}
m_NetworkSettings.WithRelayParameters(ref m_RelayServerData, m_HeartbeatTimeoutMS);
serverEndpoint = m_RelayServerData.Endpoint;
}
else
{
@@ -446,7 +508,8 @@ namespace Unity.Netcode.Transports.UTP
InitDriver();
int result = m_Driver.Bind(NetworkEndPoint.AnyIpv4);
var bindEndpoint = serverEndpoint.Family == NetworkFamily.Ipv6 ? NetworkEndpoint.AnyIpv6 : NetworkEndpoint.AnyIpv4;
int result = m_Driver.Bind(bindEndpoint);
if (result != 0)
{
Debug.LogError("Client failed to bind");
@@ -459,7 +522,7 @@ namespace Unity.Netcode.Transports.UTP
return true;
}
private bool ServerBindAndListen(NetworkEndPoint endPoint)
private bool ServerBindAndListen(NetworkEndpoint endPoint)
{
InitDriver();
@@ -481,51 +544,13 @@ namespace Unity.Netcode.Transports.UTP
return true;
}
private static RelayAllocationId ConvertFromAllocationIdBytes(byte[] allocationIdBytes)
{
unsafe
{
fixed (byte* ptr = allocationIdBytes)
{
return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length);
}
}
}
private static RelayHMACKey ConvertFromHMAC(byte[] hmac)
{
unsafe
{
fixed (byte* ptr = hmac)
{
return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length);
}
}
}
private static RelayConnectionData ConvertConnectionData(byte[] connectionData)
{
unsafe
{
fixed (byte* ptr = connectionData)
{
return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length);
}
}
}
internal void SetMaxPayloadSize(int maxPayloadSize)
{
m_MaxPayloadSize = maxPayloadSize;
}
private void SetProtocol(ProtocolType inProtocol)
{
m_ProtocolType = inProtocol;
}
/// <summary>Set the relay server data for the server.</summary>
/// <param name="ipv4Address">IP address of the relay server.</param>
/// <param name="ipv4Address">IP address or hostname of the relay server.</param>
/// <param name="port">UDP port of the relay server.</param>
/// <param name="allocationIdBytes">Allocation ID as a byte array.</param>
/// <param name="keyBytes">Allocation key as a byte array.</param>
@@ -534,39 +559,21 @@ namespace Unity.Netcode.Transports.UTP
/// <param name="isSecure">Whether the connection is secure (uses DTLS).</param>
public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocationIdBytes, byte[] keyBytes, byte[] connectionDataBytes, byte[] hostConnectionDataBytes = null, bool isSecure = false)
{
RelayConnectionData hostConnectionData;
if (!NetworkEndPoint.TryParse(ipv4Address, port, out var serverEndpoint))
{
Debug.LogError($"Invalid address {ipv4Address}:{port}");
// We set this to default to cause other checks to fail to state you need to call this
// function again.
m_RelayServerData = default;
return;
}
var allocationId = ConvertFromAllocationIdBytes(allocationIdBytes);
var key = ConvertFromHMAC(keyBytes);
var connectionData = ConvertConnectionData(connectionDataBytes);
if (hostConnectionDataBytes != null)
{
hostConnectionData = ConvertConnectionData(hostConnectionDataBytes);
}
else
{
hostConnectionData = connectionData;
}
m_RelayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure);
m_RelayServerData.ComputeNewNonce();
var hostConnectionData = hostConnectionDataBytes ?? connectionDataBytes;
m_RelayServerData = new RelayServerData(ipv4Address, port, allocationIdBytes, connectionDataBytes, hostConnectionData, keyBytes, isSecure);
SetProtocol(ProtocolType.RelayUnityTransport);
}
/// <summary>Set the relay server data (using the lower-level Unity Transport data structure).</summary>
/// <param name="serverData">Data for the Relay server to use.</param>
public void SetRelayServerData(RelayServerData serverData)
{
m_RelayServerData = serverData;
SetProtocol(ProtocolType.RelayUnityTransport);
}
/// <summary>Set the relay server data for the host.</summary>
/// <param name="ipAddress">IP address of the relay server.</param>
/// <param name="ipAddress">IP address or hostname of the relay server.</param>
/// <param name="port">UDP port of the relay server.</param>
/// <param name="allocationId">Allocation ID as a byte array.</param>
/// <param name="key">Allocation key as a byte array.</param>
@@ -578,7 +585,7 @@ namespace Unity.Netcode.Transports.UTP
}
/// <summary>Set the relay server data for the host.</summary>
/// <param name="ipAddress">IP address of the relay server.</param>
/// <param name="ipAddress">IP address or hostname of the relay server.</param>
/// <param name="port">UDP port of the relay server.</param>
/// <param name="allocationId">Allocation ID as a byte array.</param>
/// <param name="key">Allocation key as a byte array.</param>
@@ -593,7 +600,7 @@ namespace Unity.Netcode.Transports.UTP
/// <summary>
/// Sets IP and Port information. This will be ignored if using the Unity Relay and you should call <see cref="SetRelayServerData"/>
/// </summary>
/// <param name="ipv4Address">The remote IP address</param>
/// <param name="ipv4Address">The remote IP address (despite the name, can be an IPv6 address)</param>
/// <param name="port">The remote port</param>
/// <param name="listenAddress">The local listen address</param>
public void SetConnectionData(string ipv4Address, ushort port, string listenAddress = null)
@@ -613,7 +620,7 @@ namespace Unity.Netcode.Transports.UTP
/// </summary>
/// <param name="endPoint">The remote end point</param>
/// <param name="listenEndPoint">The local listen endpoint</param>
public void SetConnectionData(NetworkEndPoint endPoint, NetworkEndPoint listenEndPoint = default)
public void SetConnectionData(NetworkEndpoint endPoint, NetworkEndpoint listenEndPoint = default)
{
string serverAddress = endPoint.Address.Split(':')[0];
@@ -634,6 +641,9 @@ namespace Unity.Netcode.Transports.UTP
/// <param name="packetDelay">Packet delay in milliseconds.</param>
/// <param name="packetJitter">Packet jitter in milliseconds.</param>
/// <param name="dropRate">Packet drop percentage.</param>
#if UTP_TRANSPORT_2_0_ABOVE
[Obsolete("SetDebugSimulatorParameters is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)]
#endif
public void SetDebugSimulatorParameters(int packetDelay, int packetJitter, int dropRate)
{
if (m_Driver.IsCreated)
@@ -662,7 +672,7 @@ namespace Unity.Netcode.Transports.UTP
else
{
m_NetworkSettings.WithRelayParameters(ref m_RelayServerData, m_HeartbeatTimeoutMS);
return ServerBindAndListen(NetworkEndPoint.AnyIpv4);
return ServerBindAndListen(NetworkEndpoint.AnyIpv4);
}
}
@@ -891,7 +901,7 @@ namespace Unity.Netcode.Transports.UTP
private void ExtractNetworkMetricsForClient(ulong transportClientId)
{
var networkConnection = ParseClientId(transportClientId);
var networkConnection = ParseClientId(transportClientId);
ExtractNetworkMetricsFromPipeline(m_UnreliableFragmentedPipeline, networkConnection);
ExtractNetworkMetricsFromPipeline(m_UnreliableSequencedFragmentedPipeline, networkConnection);
ExtractNetworkMetricsFromPipeline(m_ReliableSequencedPipeline, networkConnection);
@@ -907,7 +917,11 @@ namespace Unity.Netcode.Transports.UTP
{
//Don't need to dispose of the buffers, they are filled with data pointers.
m_Driver.GetPipelineBuffers(pipeline,
#if UTP_TRANSPORT_2_0_ABOVE
NetworkPipelineStageId.Get<NetworkMetricsPipelineStage>(),
#else
NetworkPipelineStageCollection.GetStageId(typeof(NetworkMetricsPipelineStage)),
#endif
networkConnection,
out _,
out _,
@@ -934,7 +948,11 @@ namespace Unity.Netcode.Transports.UTP
}
m_Driver.GetPipelineBuffers(m_ReliableSequencedPipeline,
#if UTP_TRANSPORT_2_0_ABOVE
NetworkPipelineStageId.Get<ReliableSequencedPipelineStage>(),
#else
NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)),
#endif
networkConnection,
out _,
out _,
@@ -956,7 +974,11 @@ namespace Unity.Netcode.Transports.UTP
}
m_Driver.GetPipelineBuffers(m_ReliableSequencedPipeline,
#if UTP_TRANSPORT_2_0_ABOVE
NetworkPipelineStageId.Get<ReliableSequencedPipelineStage>(),
#else
NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)),
#endif
networkConnection,
out _,
out _,
@@ -1117,11 +1139,12 @@ namespace Unity.Netcode.Transports.UTP
// account for the overhead of its length when we store it in the send queue.
var fragmentationCapacity = m_MaxPayloadSize + BatchedSendQueue.PerMessageOverhead;
m_NetworkSettings
.WithFragmentationStageParameters(payloadCapacity: fragmentationCapacity)
.WithBaselibNetworkInterfaceParameters(
receiveQueueCapacity: m_MaxPacketQueueSize,
sendQueueCapacity: m_MaxPacketQueueSize);
m_NetworkSettings.WithFragmentationStageParameters(payloadCapacity: fragmentationCapacity);
#if !UTP_TRANSPORT_2_0_ABOVE
m_NetworkSettings.WithBaselibNetworkInterfaceParameters(
receiveQueueCapacity: m_MaxPacketQueueSize,
sendQueueCapacity: m_MaxPacketQueueSize);
#endif
#endif
}
@@ -1159,7 +1182,23 @@ namespace Unity.Netcode.Transports.UTP
var sendTarget = new SendTarget(clientId, pipeline);
if (!m_SendQueue.TryGetValue(sendTarget, out var queue))
{
queue = new BatchedSendQueue(Math.Max(m_MaxSendQueueSize, m_MaxPayloadSize));
// The maximum size of a send queue is determined according to the disconnection
// timeout. The idea being that if the send queue contains enough reliable data that
// sending it all out would take longer than the disconnection timeout, then there's
// no point storing even more in the queue (it would be like having a ping higher
// than the disconnection timeout, which is far into the realm of unplayability).
//
// The throughput used to determine what consists the maximum send queue size is
// the maximum theoritical throughput of the reliable pipeline assuming we only send
// on each update at 60 FPS, which turns out to be around 2.688 MB/s.
//
// Note that we only care about reliable throughput for send queues because that's
// the only case where a full send queue causes a connection loss. Full unreliable
// send queues are dealt with by flushing it out to the network or simply dropping
// new messages if that fails.
var maxCapacity = m_MaxSendQueueSize > 0 ? m_MaxSendQueueSize : m_DisconnectTimeoutMS * k_MaxReliableThroughput;
queue = new BatchedSendQueue(Math.Max(maxCapacity, m_MaxPayloadSize));
m_SendQueue.Add(sendTarget, queue);
}
@@ -1173,8 +1212,7 @@ namespace Unity.Netcode.Transports.UTP
var ngoClientId = NetworkManager?.TransportIdToClientId(clientId) ?? clientId;
Debug.LogError($"Couldn't add payload of size {payload.Count} to reliable send queue. " +
$"Closing connection {ngoClientId} as reliability guarantees can't be maintained. " +
$"Perhaps 'Max Send Queue Size' ({m_MaxSendQueueSize}) is too small for workload.");
$"Closing connection {ngoClientId} as reliability guarantees can't be maintained.");
if (clientId == m_ServerClientId)
{
@@ -1223,9 +1261,9 @@ namespace Unity.Netcode.Transports.UTP
}
var succeeded = ClientBindAndConnect();
if (!succeeded)
if (!succeeded && m_Driver.IsCreated)
{
Shutdown();
m_Driver.Dispose();
}
return succeeded;
}
@@ -1250,16 +1288,16 @@ namespace Unity.Netcode.Transports.UTP
{
case ProtocolType.UnityTransport:
succeeded = ServerBindAndListen(ConnectionData.ListenEndPoint);
if (!succeeded)
if (!succeeded && m_Driver.IsCreated)
{
Shutdown();
m_Driver.Dispose();
}
return succeeded;
case ProtocolType.RelayUnityTransport:
succeeded = StartRelayServer();
if (!succeeded)
if (!succeeded && m_Driver.IsCreated)
{
Shutdown();
m_Driver.Dispose();
}
return succeeded;
default:
@@ -1298,16 +1336,65 @@ namespace Unity.Netcode.Transports.UTP
m_ServerClientId = 0;
}
private void ConfigureSimulator()
#if UTP_TRANSPORT_2_0_ABOVE
private void ConfigureSimulatorForUtp2()
{
// As DebugSimulator is deprecated, the 'packetDelayMs', 'packetJitterMs' and 'packetDropPercentage'
// parameters are set to the default and are supposed to be changed using Network Simulator tool instead.
m_NetworkSettings.WithSimulatorStageParameters(
maxPacketCount: 300, // TODO Is there any way to compute a better value?
maxPacketSize: NetworkParameterConstants.MTU,
packetDelayMs: 0,
packetJitterMs: 0,
packetDropPercentage: 0,
randomSeed: DebugSimulatorRandomSeed ?? (uint)System.Diagnostics.Stopwatch.GetTimestamp()
, mode: ApplyMode.AllPackets
);
m_NetworkSettings.WithNetworkSimulatorParameters();
}
#else
private void ConfigureSimulatorForUtp1()
{
m_NetworkSettings.WithSimulatorStageParameters(
maxPacketCount: 300, // TODO Is there any way to compute a better value?
maxPacketSize: NetworkParameterConstants.MTU,
packetDelayMs: DebugSimulator.PacketDelayMS,
packetJitterMs: DebugSimulator.PacketJitterMS,
packetDropPercentage: DebugSimulator.PacketDropRate
packetDropPercentage: DebugSimulator.PacketDropRate,
randomSeed: DebugSimulatorRandomSeed ?? (uint)System.Diagnostics.Stopwatch.GetTimestamp()
);
}
#endif
private string m_ServerPrivateKey;
private string m_ServerCertificate;
private string m_ServerCommonName;
private string m_ClientCaCertificate;
/// <summary>Set the server parameters for encryption.</summary>
/// <param name="serverCertificate">Public certificate for the server (PEM format).</param>
/// <param name="serverPrivateKey">Private key for the server (PEM format).</param>
public void SetServerSecrets(string serverCertificate, string serverPrivateKey)
{
m_ServerPrivateKey = serverPrivateKey;
m_ServerCertificate = serverCertificate;
}
/// <summary>Set the client parameters for encryption.</summary>
/// <remarks>
/// If the CA certificate is not provided, validation will be done against the OS/browser
/// certificate store. This is what you'd want if using certificates from a known provider.
/// For self-signed certificates, the CA certificate needs to be provided.
/// </remarks>
/// <param name="serverCommonName">Common name of the server (typically hostname).</param>
/// <param name="caCertificate">CA certificate used to validate the server's authenticity.</param>
public void SetClientSecrets(string serverCommonName, string caCertificate = null)
{
m_ServerCommonName = serverCommonName;
m_ClientCaCertificate = caCertificate;
}
/// <summary>
/// Creates the internal NetworkDriver
@@ -1322,22 +1409,124 @@ namespace Unity.Netcode.Transports.UTP
out NetworkPipeline unreliableSequencedFragmentedPipeline,
out NetworkPipeline reliableSequencedPipeline)
{
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 && !UTP_TRANSPORT_2_0_ABOVE
NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage());
#endif
#if UNITY_EDITOR || DEVELOPMENT_BUILD
ConfigureSimulator();
#if UTP_TRANSPORT_2_0_ABOVE && UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED
ConfigureSimulatorForUtp2();
#elif !UTP_TRANSPORT_2_0_ABOVE && (UNITY_EDITOR || DEVELOPMENT_BUILD)
ConfigureSimulatorForUtp1();
#endif
m_NetworkSettings.WithNetworkConfigParameters(
maxConnectAttempts: transport.m_MaxConnectAttempts,
connectTimeoutMS: transport.m_ConnectTimeoutMS,
disconnectTimeoutMS: transport.m_DisconnectTimeoutMS,
#if UTP_TRANSPORT_2_0_ABOVE
sendQueueCapacity: m_MaxPacketQueueSize,
receiveQueueCapacity: m_MaxPacketQueueSize,
#endif
heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS);
driver = NetworkDriver.Create(m_NetworkSettings);
#if UNITY_WEBGL && !UNITY_EDITOR
if (NetworkManager.IsServer)
{
throw new Exception("WebGL as a server is not supported by Unity Transport, outside the Editor.");
}
#endif
#if UTP_TRANSPORT_2_0_ABOVE
if (m_UseEncryption)
{
if (m_ProtocolType == ProtocolType.RelayUnityTransport)
{
if (m_RelayServerData.IsSecure != 0)
{
// log an error because we have mismatched configuration
Debug.LogError("Mismatched security configuration, between Relay and local NetworkManager settings");
}
// No need to to anything else if using Relay because UTP will handle the
// configuration of the security parameters on its own.
}
else
{
try
{
if (NetworkManager.IsServer)
{
if (m_ServerCertificate.Length == 0 || m_ServerPrivateKey.Length == 0)
{
throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key.");
}
m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey);
}
else
{
if (m_ServerCommonName.Length == 0)
{
throw new Exception("In order to use encrypted communications, clients must set the server common name.");
}
else if (m_ClientCaCertificate == null)
{
m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName);
}
else
{
m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName);
}
}
}
catch(Exception e)
{
Debug.LogException(e, this);
}
}
}
#endif
#if UTP_TRANSPORT_2_0_ABOVE
if (m_UseWebSockets)
{
driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings);
}
else
{
#if UNITY_WEBGL
Debug.LogWarning($"WebSockets were used even though they're not selected in NetworkManager. You should check {nameof(UseWebSockets)}', on the Unity Transport component, to silence this warning.");
driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings);
#else
driver = NetworkDriver.Create(new UDPNetworkInterface(), m_NetworkSettings);
#endif
}
#else
driver = NetworkDriver.Create(m_NetworkSettings);
#endif
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 && UTP_TRANSPORT_2_0_ABOVE
driver.RegisterPipelineStage(new NetworkMetricsPipelineStage());
#endif
#if !UTP_TRANSPORT_2_0_ABOVE
SetupPipelinesForUtp1(driver,
out unreliableFragmentedPipeline,
out unreliableSequencedFragmentedPipeline,
out reliableSequencedPipeline);
#else
SetupPipelinesForUtp2(driver,
out unreliableFragmentedPipeline,
out unreliableSequencedFragmentedPipeline,
out reliableSequencedPipeline);
#endif
}
#if !UTP_TRANSPORT_2_0_ABOVE
private void SetupPipelinesForUtp1(NetworkDriver driver,
out NetworkPipeline unreliableFragmentedPipeline,
out NetworkPipeline unreliableSequencedFragmentedPipeline,
out NetworkPipeline reliableSequencedPipeline)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (DebugSimulator.PacketDelayMS > 0 || DebugSimulator.PacketDropRate > 0)
{
@@ -1355,7 +1544,7 @@ namespace Unity.Netcode.Transports.UTP
typeof(SimulatorPipelineStage),
typeof(SimulatorPipelineStageInSend)
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
,typeof(NetworkMetricsPipelineStage)
, typeof(NetworkMetricsPipelineStage)
#endif
);
reliableSequencedPipeline = driver.CreatePipeline(
@@ -1363,7 +1552,7 @@ namespace Unity.Netcode.Transports.UTP
typeof(SimulatorPipelineStage),
typeof(SimulatorPipelineStageInSend)
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
,typeof(NetworkMetricsPipelineStage)
, typeof(NetworkMetricsPipelineStage)
#endif
);
}
@@ -1373,25 +1562,63 @@ namespace Unity.Netcode.Transports.UTP
unreliableFragmentedPipeline = driver.CreatePipeline(
typeof(FragmentationPipelineStage)
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
,typeof(NetworkMetricsPipelineStage)
, typeof(NetworkMetricsPipelineStage)
#endif
);
unreliableSequencedFragmentedPipeline = driver.CreatePipeline(
typeof(FragmentationPipelineStage),
typeof(UnreliableSequencedPipelineStage)
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
,typeof(NetworkMetricsPipelineStage)
, typeof(NetworkMetricsPipelineStage)
#endif
);
reliableSequencedPipeline = driver.CreatePipeline(
typeof(ReliableSequencedPipelineStage)
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
,typeof(NetworkMetricsPipelineStage)
, typeof(NetworkMetricsPipelineStage)
#endif
);
}
}
#else
private void SetupPipelinesForUtp2(NetworkDriver driver,
out NetworkPipeline unreliableFragmentedPipeline,
out NetworkPipeline unreliableSequencedFragmentedPipeline,
out NetworkPipeline reliableSequencedPipeline)
{
unreliableFragmentedPipeline = driver.CreatePipeline(
typeof(FragmentationPipelineStage)
#if UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED
, typeof(SimulatorPipelineStage)
#endif
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
, typeof(NetworkMetricsPipelineStage)
#endif
);
unreliableSequencedFragmentedPipeline = driver.CreatePipeline(
typeof(FragmentationPipelineStage),
typeof(UnreliableSequencedPipelineStage)
#if UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED
, typeof(SimulatorPipelineStage)
#endif
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
, typeof(NetworkMetricsPipelineStage)
#endif
);
reliableSequencedPipeline = driver.CreatePipeline(
typeof(ReliableSequencedPipelineStage)
#if UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED
, typeof(SimulatorPipelineStage)
#endif
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
, typeof(NetworkMetricsPipelineStage)
#endif
);
}
#endif
// -------------- Utility Types -------------------------------------------------------------------------------

View File

@@ -30,6 +30,11 @@
"name": "com.unity.multiplayer.tools",
"expression": "1.0.0-pre.7",
"define": "MULTIPLAYER_TOOLS_1_0_0_PRE_7"
},
{
"name": "com.unity.transport",
"expression": "2.0.0-exp",
"define": "UTP_TRANSPORT_2_0_ABOVE"
}
]
}