Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

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

## [Unreleased]
## [2.7.0] - 2025-10-27

### Added

Expand All @@ -33,6 +33,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Added

- Adding this as a mock change.
- `RpcInvokePermission` to control who has permission to invoke specific RPC methods. (#3731)
- Added NetworkRigidbody documentation section. (#3664)
- Added new fields to the `SceneMap` struct when using Unity 6.3 or higher. These fields allow referencing scene handles via the new `SceneHandle` struct. (#3734)
Expand Down Expand Up @@ -60,6 +61,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Initialization errors with NetworkAnimator. (#3767)
- Multiple disconnect events from the same transport will no longer disconnect the host. (#3707)
- Fixed NetworkTransform state synchronization issue when `NetworkTransform.SwitchTransformSpaceWhenParented` is enabled and the associated NetworkObject is parented multiple times in a single frame or within a couple of frames. (#3664)
- Fixed issue when spawning, parenting, and immediately re-parenting when `NetworkTransform.SwitchTransformSpaceWhenParented` is enabled. (#3664)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1654,34 +1654,25 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio
return null;
}

var typeSystem = methodDefinition.Module.TypeSystem;
var hasInvokePermission = false;
bool hasInvokePermission = false, hasRequireOwnership = false;

CustomAttributeNamedArgument? invokePermissionAttribute = null;
foreach (var argument in rpcAttribute.Fields)
{
switch (argument.Name)
{
case k_ServerRpcAttribute_RequireOwnership:
var requireOwnership = argument.Argument.Type == typeSystem.Boolean && (bool)argument.Argument.Value;
var invokePermissionArg = new CustomAttributeArgument(m_RpcInvokePermissions_TypeRef, requireOwnership ? RpcInvokePermission.Owner : RpcInvokePermission.Everyone);
invokePermissionAttribute = new CustomAttributeNamedArgument(k_RpcAttribute_InvokePermission, invokePermissionArg);
hasRequireOwnership = true;
break;
case k_RpcAttribute_InvokePermission:
hasInvokePermission = true;
break;
}
}

if (invokePermissionAttribute != null)
if (hasInvokePermission && hasRequireOwnership)
{
if (hasInvokePermission)
{
m_Diagnostics.AddError($"{methodDefinition.Name} cannot declare both RequireOwnership and InvokePermission!");
return null;
}

rpcAttribute.Fields.Add(invokePermissionAttribute.Value);
m_Diagnostics.AddError($"{methodDefinition.Name} cannot declare both RequireOwnership and InvokePermission!");
return null;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -720,13 +720,16 @@ internal AnimationMessage GetAnimationMessage()
return m_AnimationMessage;
}

/// <inheritdoc/>
public override void OnNetworkSpawn()
internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManager)
{
// Save internal state references
m_LocalNetworkManager = NetworkManager;
m_LocalNetworkManager = networkManager;
DistributedAuthorityMode = m_LocalNetworkManager.DistributedAuthorityMode;
}

/// <inheritdoc/>
public override void OnNetworkSpawn()
{
// If there is no assigned Animator then generate a server network warning (logged locally and if applicable on the server-host side as well).
if (m_Animator == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4230,8 +4230,14 @@ internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator()
// Non-Authority
private void UpdateInterpolation()
{
// Select the time system relative to the type of NetworkManager instance.
var timeSystem = m_CachedNetworkManager.IsServer ? m_CachedNetworkManager.LocalTime : m_CachedNetworkManager.ServerTime;
// Use the local time because:
// Client-Server:
// Local time is server time on a host or server.
// Local time on clients takes latency into consideration.
// Distributed authority:
// Local time is used by the authority.
// Local time on non-authority takes latency into consid]eration.
var timeSystem = m_CachedNetworkManager.LocalTime;
var currentTime = timeSystem.Time;
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,37 @@ public sealed class NetworkConnectionManager
private static ProfilerMarker s_TransportDisconnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportDisconnect");
#endif

private string m_DisconnectReason;
/// <summary>
/// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
/// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
/// provide disconnect information that will be followed by the server's disconnect reason.
/// </summary>
public string DisconnectReason { get; internal set; }
/// <remarks>
/// On a server or host, this value could no longer exist after all subscribed callbacks are invoked for the
/// client that disconnected. It is recommended to copy the message to some other property or field when
/// <see cref="OnClientDisconnectCallback"/> is invoked.
/// </remarks>
public string DisconnectReason => GetDisconnectReason(); // fine as function because this call is infrequent

/// <summary>
/// Gets the reason for why this client was disconnected if exists.
/// </summary>
/// <returns><see cref="ServerDisconnectReason"/> disconnect reason if it exists, otherwise <see cref="m_DisconnectReason"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal string GetDisconnectReason()
{
// TODO: fix this properly
if (!string.IsNullOrEmpty(ServerDisconnectReason))
{
return ServerDisconnectReason;
}
return m_DisconnectReason;
}

/// <summary>
/// Updated by <see cref="DisconnectReasonMessage"/>.
/// </summary>
internal string ServerDisconnectReason;

/// <summary>
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
Expand Down Expand Up @@ -537,21 +563,20 @@ internal void DataEventHandler(ulong transportClientId, ref ArraySegment<byte> p
private void GenerateDisconnectInformation(ulong clientId, ulong transportClientId, string reason = null)
{
var header = $"[Disconnect Event][Client-{clientId}][TransportClientId-{transportClientId}]";
var existingDisconnectReason = DisconnectReason;

var defaultMessage = Transport.DisconnectEventMessage;
if (reason != null)
{
defaultMessage = $"{reason} {defaultMessage}";
}

// Just go ahead and set this whether client or server so any subscriptions to a disconnect event can check the DisconnectReason
// to determine why the client disconnected
DisconnectReason = $"{header}[{Transport.DisconnectEvent}] {defaultMessage}";
DisconnectReason = $"{DisconnectReason}\n{existingDisconnectReason}";
m_DisconnectReason = $"{header}[{Transport.DisconnectEvent}] {defaultMessage}";

if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"{DisconnectReason}");
var serverDisconnectReason = string.IsNullOrEmpty(ServerDisconnectReason) ? string.Empty : $"\n{ServerDisconnectReason}";
NetworkLog.LogInfo($"{m_DisconnectReason}{serverDisconnectReason}");
}
}

Expand Down Expand Up @@ -1475,7 +1500,8 @@ internal void Initialize(NetworkManager networkManager)
TransportIdToClientIdMap.Clear();
ClientsToApprove.Clear();
NetworkObject.OrphanChildren.Clear();
DisconnectReason = string.Empty;
m_DisconnectReason = string.Empty;
ServerDisconnectReason = string.Empty;

NetworkManager = networkManager;
MessageManager = networkManager.MessageManager;
Expand Down
17 changes: 16 additions & 1 deletion com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,11 @@ internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams,
throw new RpcException("This RPC can only be sent by the server.");
}

if (attributeParams.InvokePermission == RpcInvokePermission.Owner && !IsOwner)
#pragma warning disable CS0618 // Type or member is obsolete
var requireOwnership = attributeParams.RequireOwnership;
#pragma warning restore CS0618 // Type or member is obsolete

if ((requireOwnership || attributeParams.InvokePermission == RpcInvokePermission.Owner) && !IsOwner)
{
throw new RpcException("This RPC can only be sent by its owner.");
}
Expand Down Expand Up @@ -749,6 +753,8 @@ public virtual void OnNetworkDespawn() { }
/// </summary>
public virtual void OnNetworkPreDespawn() { }

internal virtual void InternalOnNetworkPreSpawn(ref NetworkManager networkManager) { }

internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject networkObject)
{
m_NetworkObject = networkObject;
Expand All @@ -757,6 +763,15 @@ internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject n

UpdateNetworkProperties();

InternalOnNetworkPreSpawn(ref networkManager);

// Exit early for disabled NetworkBehaviours.
// We still want the above values to be set.
if (!gameObject.activeInHierarchy)
{
return;
}

try
{
OnNetworkPreSpawn(ref networkManager);
Expand Down
20 changes: 13 additions & 7 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,12 @@ internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNe
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
// Any NetworkBehaviour that is not spawned and the associated GameObject is disabled should be
// skipped over (i.e. not supported).
if (!ChildNetworkBehaviours[i].IsSpawned && !ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
continue;
}
// Invoke internal notification
ChildNetworkBehaviours[i].InternalOnNetworkObjectParentChanged(parentNetworkObject);
// Invoke public notification
Expand Down Expand Up @@ -2217,7 +2223,6 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true)
// DANGO-TODO: Do we want to worry about ownership permissions here?
// It wouldn't make sense to not allow parenting, but keeping this note here as a reminder.
var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner);
Debug.Log($"something is broken! isAuthority={isAuthority} | HasAuthority={HasAuthority} | (AllowOwnerToParent && IsOwner)={(AllowOwnerToParent && IsOwner)}");

// If we don't have authority and we are not shutting down, then don't allow any parenting.
// If we are shutting down and don't have authority then allow it.
Expand Down Expand Up @@ -2543,10 +2548,7 @@ internal void InvokeBehaviourNetworkPreSpawn()
var networkManager = NetworkManager;
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager, this);
}
ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager, this);
}
}

Expand All @@ -2558,10 +2560,9 @@ internal void InvokeBehaviourNetworkSpawn()
{
if (!childBehaviour.gameObject.activeInHierarchy)
{
Debug.LogWarning($"{childBehaviour.gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during spawn!");
Debug.LogWarning($"{GenerateDisabledNetworkBehaviourWarning(childBehaviour)}");
continue;
}

childBehaviour.InternalOnNetworkSpawn();
}
}
Expand Down Expand Up @@ -2619,6 +2620,11 @@ internal void InvokeBehaviourNetworkDespawn()

private List<NetworkBehaviour> m_ChildNetworkBehaviours;

internal string GenerateDisabledNetworkBehaviourWarning(NetworkBehaviour networkBehaviour)
{
return $"[{name}][{networkBehaviour.GetType().Name}][{nameof(isActiveAndEnabled)}: {networkBehaviour.isActiveAndEnabled}] Disabled {nameof(NetworkBehaviour)}s will be excluded from spawning and synchronization!";
}

internal List<NetworkBehaviour> ChildNetworkBehaviours
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion)
{
string reasonSent = Reason ?? string.Empty;

// Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message itself.
// Since we don't send a ConnectionApprovedMessage, the version for this message is encoded with the message itself.
// However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion on this side of things.
// We just have to make sure the receiving side knows what version we sent it, since whoever has the higher version number is responsible for versioning and they may be the one with the higher version number.
BytePacker.WriteValueBitPacked(writer, Version);
Expand All @@ -37,7 +37,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int

public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).ConnectionManager.DisconnectReason = Reason;
// Always apply the server-side generated disconnect reason to the server specific disconnect reason.
// This is combined with the additional disconnect information when getting NetworkManager.DisconnectReason
// (NetworkConnectionManager.DisconnectReason).
((NetworkManager)context.SystemOwner).ConnectionManager.ServerDisconnectReason = Reason;
}
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#if !MULTIPLAYER_TOOLS
using System.IO;
using System.Reflection;
using NUnit.Framework;
Expand Down Expand Up @@ -41,4 +40,3 @@ public void BasicBuildTest()
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ protected override void OnServerAndClientsCreated()
private void Client_OnClientDisconnectCallback(ulong clientId)
{
m_ClientNetworkManagers[0].OnClientDisconnectCallback -= Client_OnClientDisconnectCallback;
m_ClientDisconnectReasonValidated = m_ClientNetworkManagers[0].LocalClientId == clientId && m_ClientNetworkManagers[0].DisconnectReason == k_InvalidToken;
m_ClientDisconnectReasonValidated = m_ClientNetworkManagers[0].LocalClientId == clientId && m_ClientNetworkManagers[0].DisconnectReason.Contains(k_InvalidToken);
}

private bool ClientAndHostValidated()
Expand Down
Loading
Loading