Skip to content

Commit

Permalink
feat: Add a SessionOwner ObjectStatus and allow InScenePlaced network…
Browse files Browse the repository at this point in the history
… objects to be distributed (#3175)

* Initial pass on SessionOwner ownership flag

* feat: Add SessionOwner OwnershipStatus flag

* update

removing additional session owner accessor.

* Add OwnershipPermissions tests

* fix client connect

* Revert "fix client connect"

This reverts commit 3c3b354.

* update

object distribution for in-scene placed NetworkObjects needs to use the InScenePlacedSourceGlobalObjectIdHash when building an object distribution list.

* Add changelog

* Remove unnecessary change

* Remove Settings.json

* Reword CHANGELOG

* fix

DAHost should not distribute session owner permission NetworkObjects.
When client is promoted to session owner, for now newly promoted client takes ownership of NetworkObjects that have the SessionOwner permission set.
Only prevent non-session owners from taking ownership of a NetworkObject with the SessionOwner permission set.

* test fix

Avoid the RemoveOwnership client-server only method.

* style

Visual studio code cleanup likes to sort by alpha... fixing for our standards.

* update

Adding check for session owner trying to change ownership to a non-session owner client.

* test

Adding an additional validation that a non-session owner cannot change ownership and that a session owner cannot change ownership to a non-session owner when the NetworkObject in question has the SessionOwner permissions set.

---------

Co-authored-by: NoelStephensUnity <[email protected]>
  • Loading branch information
EmandM and NoelStephensUnity authored Dec 13, 2024
1 parent 2b3893c commit 2d10975
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 88 deletions.
2 changes: 2 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Added

- Added `NetworkObject.OwnershipStatus.SessionOwner` to allow Network Objects to be distributable and only owned by the Session Owner. This flag will override all other `OwnershipStatus` flags. (#3175)
- Added `UnityTransport.GetEndpoint` method to provide a way to obtain `NetworkEndpoint` information of a connection via client identifier. (#3130)
- Added `NetworkTransport.OnEarlyUpdate` and `NetworkTransport.OnPostLateUpdate` methods to provide more control over handling transport related events at the start and end of each frame. (#3113)

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

### Changed

- In-scene placed `NetworkObject`s have been made distributable when balancing object distribution after a connection event. (#3175)
- Optimised `NetworkVariable` and `NetworkTransform` related packets when in Distributed Authority mode.
- The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121)

Expand Down
6 changes: 5 additions & 1 deletion com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ public override void OnInspectorGUI()
EditorGUI.BeginChangeCheck();
serializedObject.UpdateIfRequiredOrScript();
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
if (m_NetworkObject.IsOwnershipSessionOwner)
{
m_NetworkObject.Ownership = NetworkObject.OwnershipStatus.SessionOwner;
}
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();

Expand Down Expand Up @@ -193,7 +197,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten

// The below can cause visual anomalies and/or throws an exception within the EditorGUI itself (index out of bounds of the array). and has
// The visual anomaly is when you select one field it is set in the drop down but then the flags selection in the popup menu selects more items
// even though if you exit the popup menu the flag setting is correct.
// even though if you exit the popup menu the flag setting is correct.
//var ownership = (NetworkObject.OwnershipStatus)EditorGUI.EnumFlagsField(position, label, (NetworkObject.OwnershipStatus)property.enumValueFlag);
//property.enumValueFlag = (int)ownership;
EditorGUI.EndDisabledGroup();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Unity.Netcode
{
/// <summary>
/// The connection event type set within <see cref="ConnectionEventData"/> to signify the type of connection event notification received.
/// The connection event type set within <see cref="ConnectionEventData"/> to signify the type of connection event notification received.
/// </summary>
/// <remarks>
/// <see cref="ConnectionEventData"/> is returned as a parameter of the <see cref="NetworkManager.OnConnectionEvent"/> event notification.
Expand Down Expand Up @@ -1212,7 +1212,7 @@ internal void OnClientDisconnectFromServer(ulong clientId)
{
// Only NetworkObjects that have the OwnershipStatus.Distributable flag set and no parent
// (ownership is transferred to all children) will have their ownership redistributed.
if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null)
if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null && !ownedObject.IsOwnershipSessionOwner)
{
if (ownedObject.IsOwnershipLocked)
{
Expand Down Expand Up @@ -1249,6 +1249,11 @@ internal void OnClientDisconnectFromServer(ulong clientId)
childObject.SetOwnershipLock(false);
}

// Ignore session owner marked objects
if (childObject.IsOwnershipSessionOwner)
{
continue;
}
NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true);
if (EnableDistributeLogging)
{
Expand Down
8 changes: 2 additions & 6 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,7 @@ internal void SetSessionOwner(ulong sessionOwner)
foreach (var networkObjectEntry in SpawnManager.SpawnedObjects)
{
var networkObject = networkObjectEntry.Value;
if (networkObject.IsSceneObject == null || !networkObject.IsSceneObject.Value)
{
continue;
}
if (networkObject.OwnerClientId != LocalClientId)
if (networkObject.IsOwnershipSessionOwner && LocalClient.IsSessionOwner)
{
SpawnManager.ChangeOwnership(networkObject, LocalClientId, true);
}
Expand Down Expand Up @@ -416,7 +412,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)
// Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed.
MetricsManager.UpdateMetrics();

// Handle sending any pending transport messages
// Handle sending any pending transport messages
NetworkConfig.NetworkTransport.PostLateUpdate();

// TODO: Determine a better way to handle this
Expand Down
62 changes: 50 additions & 12 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private static void PrefabStageOpened(PrefabStage prefabStage)
/// <remarks>
/// InContext: Typically means a are in prefab edit mode for an in-scene placed network prefab instance.
/// (currently no such thing as a network prefab with nested network prefab instances)
///
///
/// InIsolation: Typically means we are in prefb edit mode for a prefab asset.
/// </remarks>
/// <param name="prefabStage"></param>
Expand Down Expand Up @@ -439,6 +439,13 @@ public void DeferDespawn(int tickOffset, bool destroy = true)
/// </remarks>
public bool IsOwnershipDistributable => Ownership.HasFlag(OwnershipStatus.Distributable);

/// <summary>
/// When true, the <see cref="NetworkObject"/> can only be owned by the current Session Owner.
/// To set <see cref="OwnershipStatus.SessionOwner"/> during runtime, use <see cref="ChangeOwnership(ulong)"/> to ensure the session owner owns the object.
/// Once the session owner owns the object, then use <see cref="SetOwnershipStatus(OwnershipStatus, bool, OwnershipLockActions)"/>.
/// </summary>
public bool IsOwnershipSessionOwner => Ownership.HasFlag(OwnershipStatus.SessionOwner);

/// <summary>
/// Returns true if the <see cref="NetworkObject"/> is has ownership locked.
/// When locked, the <see cref="NetworkObject"/> cannot be redistributed nor can it be transferred by another client.
Expand Down Expand Up @@ -481,7 +488,8 @@ public void DeferDespawn(int tickOffset, bool destroy = true)
/// <see cref="None"/>: If nothing is set, then ownership is considered "static" and cannot be redistributed, requested, or transferred (i.e. a Player would have this).
/// <see cref="Distributable"/>: When set, this instance will be automatically redistributed when a client joins (if not locked or no request is pending) or leaves.
/// <see cref="Transferable"/>: When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked).
/// <see cref="RequestRequired"/>: When set, When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred).
/// <see cref="RequestRequired"/>: When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred).
/// <see cref="SessionOwner"/>: When set, only the current session owner may have ownership over this object.
/// </summary>
// Ranges from 1 to 8 bits
[Flags]
Expand All @@ -491,6 +499,7 @@ public enum OwnershipStatus
Distributable = 1 << 0,
Transferable = 1 << 1,
RequestRequired = 1 << 2,
SessionOwner = 1 << 3,
}

/// <summary>
Expand Down Expand Up @@ -549,7 +558,7 @@ public bool SetOwnershipLock(bool lockOwnership = true)
}

// If we don't have the Transferable flag set and it is not a player object, then it is the same as having a static lock on ownership
if (!IsOwnershipTransferable && !IsPlayerObject)
if (!(IsOwnershipTransferable || IsPlayerObject) || IsOwnershipSessionOwner)
{
NetworkLog.LogWarning($"Trying to add or remove ownership lock on [{name}] which does not have the {nameof(OwnershipStatus.Transferable)} flag set!");
return false;
Expand Down Expand Up @@ -582,13 +591,15 @@ public bool SetOwnershipLock(bool lockOwnership = true)
/// <see cref="RequestRequired"/>: The <see cref="NetworkObject"/> requires an ownership request via <see cref="RequestOwnership"/>.
/// <see cref="RequestInProgress"/>: The <see cref="NetworkObject"/> is already processing an ownership request and ownership cannot be acquired at this time.
/// <see cref="NotTransferrable"/>: The <see cref="NetworkObject"/> does not have the <see cref="OwnershipStatus.Transferable"/> flag set and ownership cannot be acquired.
/// <see cref="SessionOwnerOnly"/>: The <see cref="NetworkObject"/> has the <see cref="OwnershipStatus.SessionOwner"/> flag set and ownership cannot be acquired.
/// </summary>
public enum OwnershipPermissionsFailureStatus
{
Locked,
RequestRequired,
RequestInProgress,
NotTransferrable
NotTransferrable,
SessionOwnerOnly
}

/// <summary>
Expand All @@ -610,6 +621,7 @@ public enum OwnershipPermissionsFailureStatus
/// <see cref="RequestRequiredNotSet"/>: The <see cref="OwnershipStatus.RequestRequired"/> flag is not set on this <see cref="NetworkObject"/>
/// <see cref="Locked"/>: The current owner has locked ownership which means requests are not available at this time.
/// <see cref="RequestInProgress"/>: There is already a known request in progress. You can scan for ownership changes and try upon
/// <see cref="SessionOwnerOnly"/>: This object is marked as SessionOwnerOnly and therefore cannot be requested
/// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership.
/// </summary>
public enum OwnershipRequestStatus
Expand All @@ -619,6 +631,7 @@ public enum OwnershipRequestStatus
RequestRequiredNotSet,
Locked,
RequestInProgress,
SessionOwnerOnly,
}

/// <summary>
Expand All @@ -631,6 +644,7 @@ public enum OwnershipRequestStatus
/// <see cref="OwnershipRequestStatus.RequestRequiredNotSet"/>: The <see cref="OwnershipStatus.RequestRequired"/> flag is not set on this <see cref="NetworkObject"/>
/// <see cref="OwnershipRequestStatus.Locked"/>: The current owner has locked ownership which means requests are not available at this time.
/// <see cref="OwnershipRequestStatus.RequestInProgress"/>: There is already a known request in progress. You can scan for ownership changes and try upon
/// <see cref="OwnershipRequestStatus.SessionOwnerOnly"/>: This object can only belong the the session owner and so cannot be requested
/// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership.
/// </remarks>
/// <returns><see cref="OwnershipRequestStatus"/></returns>
Expand Down Expand Up @@ -660,6 +674,12 @@ public OwnershipRequestStatus RequestOwnership()
return OwnershipRequestStatus.RequestInProgress;
}

// Exit early if it has the SessionOwner flag
if (IsOwnershipSessionOwner)
{
return OwnershipRequestStatus.SessionOwnerOnly;
}

// Otherwise, send the request ownership message
var changeOwnership = new ChangeOwnershipMessage
{
Expand Down Expand Up @@ -716,7 +736,7 @@ internal void OwnershipRequest(ulong clientRequestingOwnership)
{
response = OwnershipRequestResponseStatus.RequestInProgress;
}
else if (!IsOwnershipRequestRequired && !IsOwnershipTransferable)
else if (!(IsOwnershipRequestRequired || IsOwnershipTransferable) || IsOwnershipSessionOwner)
{
response = OwnershipRequestResponseStatus.CannotRequest;
}
Expand Down Expand Up @@ -836,6 +856,12 @@ public enum OwnershipLockActions
/// </remarks>
public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, OwnershipLockActions lockAction = OwnershipLockActions.None)
{
if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManager.LocalClient.IsSessionOwner)
{
NetworkLog.LogWarning("Only the session owner is allowed to set the ownership status to session owner only.");
return false;
}

// If it already has the flag do nothing
if (!clearAndSet && Ownership.HasFlag(status))
{
Expand All @@ -847,13 +873,25 @@ public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false,
Ownership = OwnershipStatus.None;
}

// Faster to just OR a None status than to check
// if it is !None before "OR'ing".
Ownership |= status;

if (lockAction != OwnershipLockActions.None)
if (status.HasFlag(OwnershipStatus.SessionOwner))
{
Ownership = OwnershipStatus.SessionOwner;
}
else if (Ownership.HasFlag(OwnershipStatus.SessionOwner))
{
NetworkLog.LogWarning("No other ownership statuses may be set while SessionOwner is set.");
return false;
}
else
{
SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock);
// Faster to just OR a None status than to check
// if it is !None before "OR'ing".
Ownership |= status;

if (lockAction != OwnershipLockActions.None)
{
SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock);
}
}

SendOwnershipStatusUpdate();
Expand Down Expand Up @@ -1629,7 +1667,7 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla
// DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will automatically set DistributeOwnership.");
NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will set ownership to SessionOwner.");
}
}
}
Expand Down
Loading

0 comments on commit 2d10975

Please sign in to comment.