Skip to content

feat: Add a SessionOwner ObjectStatus and allow InScenePlaced network objects to be distributed #3175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
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
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
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
Loading