Skip to content
Open
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
5 changes: 5 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Added

- Added additional section under Project Settings > Multiplayer > Netcode for GameObjects, shown when Netcode for Entities is installed and provides users a way to restor back to the recommended settings. (#4144)
- Added alignment of the Netcode for Entities tick rates with `NetworkConfig.TickRate` when a session with `GhostObject` prefabs is started, so ghost updates land on the same (relative) interval as the rest of Netcode for GameObjects. (#4144)


### Changed

- All editor assembly definitions are renamed with `Unity.Netcode.GameObjects.x` variants
Expand All @@ -32,6 +36,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093)
- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093)
- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093)
- Issue where the hybrid mode `NetCodeConfig` validation messages were not interpolated and did not check that automatic bootstrapping was disabled.

### Security

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#if UNIFIED_NETCODE
using Unity.NetCode;
using UnityEditor;
using UnityEngine;

namespace Unity.Netcode.GameObjects.Editor.Configuration
{
/// <summary>
/// Writes the <see cref="NetCodeConfig"/> values NGO recommends for hybrid mode, once, the first time a
/// <see cref="NetCodeConfig"/> is available.
/// </summary>
/// <remarks>
/// This does not create <see cref="NetCodeConfig"/>. This finds the one N4E created and modifies it.
/// Nothing tracks the project after that write. The defaults are inert in a project with no hybrid prefabs, and
/// <see cref="NetworkManager"/> re-aligns the tick rate at start-up in a project that has them, so there is no
/// reason to scan for ghost prefabs from the editor.
/// </remarks>
Comment on lines +12 to +17

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <remarks>
/// This does not create <see cref="NetCodeConfig"/>. This finds the one N4E created and modifies it.
/// Nothing tracks the project after that write. The defaults are inert in a project with no hybrid prefabs, and
/// <see cref="NetworkManager"/> re-aligns the tick rate at start-up in a project that has them, so there is no
/// reason to scan for ghost prefabs from the editor.
/// </remarks>

internal static class HybridNetcodeConfigApplier
{
/// <summary>
/// Whether the user has to opt into the experimental unified netcode API before NGO writes anything.
/// </summary>
/// <remarks>
/// TODO-RELEASE: Set this to true before the 6000.7.0 release manifest submission if Netcode for Entities

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a list of those somewhere? Otherwise I will remember to do a check around tomorrow/friday

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it going to be shipped as experimental in 6000.7?

/// ships the unified API as experimental and its scripting defines.
/// Note: This is deliberately not a const: IDE0035 (remove unreachable code) is an error in this repository, so a
/// const would fail the standards job as soon as it was set to false.
Comment on lines +24 to +27

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be tracked somewhere

/// </remarks>
internal static readonly bool RequiresExperimentalOptIn = false;

private static NetCodeConfig s_ScannedConfig;
private static bool s_ConfigScanned;

[InitializeOnLoadMethod]
private static void OnApplicationStart()
{
// Cross-assembly ordering between the two is not a documented contract.
// Defer rather than racing it.
EditorApplication.delayCall += OnDelayCall;
}

private static void OnDelayCall()
{
EditorApplication.delayCall -= OnDelayCall;
ApplyDefaults(false);
}

/// <summary>
/// Writes the NGO hybrid mode defaults into the project's <see cref="NetCodeConfig"/>.
/// </summary>
/// <param name="force">
/// Driven by the button in Project Settings:
/// - When true: re-applies the full tuned set even though this project has already had it applied once.
/// - When false: writes only if this project has never had them written. From that point forward, the user's
/// edits are not overwritten.
/// </param>
internal static void ApplyDefaults(bool force)
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}

var settings = NetcodeForGameObjectsProjectSettings.instance;
if (RequiresExperimentalOptIn && !settings.EnableUnifiedNetcodeApi)
{
return;
}

if (!force && settings.HybridDefaultsVersion >= HybridNetcodeDefaults.Version)
{
return;
}

// A project with no config yet leaves the marker unrecorded so that the next domain reload tries again.
// N4E creates one on any domain reload that finds none.
var config = ResolveGlobalConfig();
if (config == null)
{
return;
}

if (HybridNetcodeDefaults.ApplyRecommended(config, HybridNetcodeDefaults.DefaultTickRate))
Comment thread
EmandM marked this conversation as resolved.
{
EditorUtility.SetDirty(config);
AssetDatabase.SaveAssetIfDirty(config);
Debug.Log($"[Netcode] Applied the NGO hybrid mode defaults to '{config.name}'. These are tuned for NGO and can be changed freely; they will not be re-applied automatically. Use Project Settings > Multiplayer > Netcode for GameObjects to restore them.", config);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use the new logger. Maybe this log should be at Developer level?

}

// Recorded even when the config already matched and nothing was written. Leaving it unrecorded would make
// the next domain reload a first application again, which would revert the user's next edit.
settings.HybridDefaultsVersion = HybridNetcodeDefaults.Version;
settings.SaveSettings();
}

/// <summary>
/// Resolves the config N4E considers global, falling back to a project scan when N4E has not assigned one yet.
/// </summary>
/// <remarks>
/// The scan is done at most once per domain reload, including when it finds nothing, because this is also
/// reached from OnGUI and <see cref="AssetDatabase.FindAssets"/> walks the entire project. A config created
/// after the scan is picked up on the next domain reload.
/// </remarks>
/// <returns>The config to adjust or null if no config exists.</returns>
internal static NetCodeConfig ResolveGlobalConfig()
{
if (NetCodeConfig.Global != null)
{
return NetCodeConfig.Global;
}

if (!s_ConfigScanned)
{
s_ConfigScanned = true;
var guids = AssetDatabase.FindAssets($"t:{nameof(NetCodeConfig)}");
s_ScannedConfig = guids.Length == 1 ? AssetDatabase.LoadAssetAtPath<NetCodeConfig>(AssetDatabase.GUIDToAssetPath(guids[0])) : null;
}
Comment on lines +112 to +117

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need this logic, N4E has their own logic to find a NetcodeConfig file and assign it as the global file. That logic is run on InitializeOnLoad and on RuntimeInitializeOnLoad.

NGO should trust the contract that the Global field is always set. If there are situations where the Global field isn't set we file it as a bug and fix it in N4E.


return s_ScannedConfig;
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ private void OnEnable()
[SerializeField]
public bool GenerateDefaultNetworkPrefabs = true;

#if UNIFIED_NETCODE
/// <summary>
/// Whether the user has opted into the experimental unified netcode API.
/// </summary>
/// <remarks>
/// Only consulted while <see cref="HybridNetcodeConfigApplier.RequiresExperimentalOptIn"/> holds. Turning it
/// off again hides the hybrid section and leaves the NetCodeConfig exactly as it is; the marker below is what
/// keeps turning it back on from overwriting anything.
/// </remarks>
[SerializeField]
public bool EnableUnifiedNetcodeApi;

/// <summary>
/// The hybrid mode default values already applied to this project's NetCodeConfig.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment implies that the field caches values, but the field is an int, not a struct.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field is written to when loading NGO v3.x.x for the 1st time. It signifies the default settings have been applied.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it an int rather than a bool?

/// </summary>
/// <remarks>
/// Zero means they have never been applied. Persisting this value is what keeps the tuned values a one-shot.
/// For users who deliberately change them, they are not overwritten on the next domain reload.
/// </remarks>
[SerializeField]
public int HybridDefaultsVersion;
Comment thread
EmandM marked this conversation as resolved.
#endif

internal void SaveSettings()
{
Save(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.IO;
#if UNIFIED_NETCODE
using Unity.NetCode;
#endif
using UnityEditor;
using UnityEngine;
using Directory = UnityEngine.Windows.Directory;
Expand All @@ -11,6 +14,9 @@ internal static class NetcodeSettingsProvider
{
private const float k_MaxLabelWidth = 450f;
private static float s_MaxLabelWidth;
#if UNIFIED_NETCODE
private static float s_HybridLabelWidth;
#endif
private static bool s_ShowEditorSettingFields = true;
private static bool s_ShowProjectSettingFields = true;

Expand Down Expand Up @@ -192,6 +198,10 @@ private static void OnGuiHandler(string obj)
networkPrefabsPath,
GUILayout.Width(s_MaxLabelWidth + 270));
GUILayout.EndVertical();

#if UNIFIED_NETCODE
DrawHybridSettings(settings);
#endif
}
EditorGUILayout.EndFoldoutHeaderGroup();
GUILayout.EndVertical();
Expand All @@ -205,6 +215,84 @@ private static void OnGuiHandler(string obj)
settings.SaveSettings();
}
}

#if UNIFIED_NETCODE
/// <summary>
/// Displays the NetCodeConfig the NGO hybrid mode defaults were written into, and offers a way to restore
/// those defaults for anyone who has since changed them.
/// </summary>
/// <param name="settings">The project settings holding the opt-in flag and the applied-defaults marker.</param>
private static void DrawHybridSettings(NetcodeForGameObjectsProjectSettings settings)
{
if (HybridNetcodeConfigApplier.RequiresExperimentalOptIn && !DrawUnifiedNetcodeApiToggle(settings))
{
return;
}

GUILayout.BeginVertical("Box");
GUILayout.Label("Hybrid (Netcode for Entities)", EditorStyles.boldLabel);

var config = HybridNetcodeConfigApplier.ResolveGlobalConfig();
if (config == null)
{
EditorGUILayout.HelpBox("No NetCodeConfig could be resolved. Open Project Settings > Multiplayer, which creates one, then reload the project.", MessageType.Warning);
GUILayout.EndVertical();
return;
}

EditorGUILayout.ObjectField(new GUIContent("Applied to", "The NetCodeConfig that Netcode for GameObjects wrote its hybrid mode defaults into."), config, typeof(NetCodeConfig), false);

if (settings.HybridDefaultsVersion < HybridNetcodeDefaults.Version)
{
EditorGUILayout.HelpBox("The Netcode for GameObjects hybrid defaults have not been applied to this config yet.", MessageType.Info);
}

if (GUILayout.Button(new GUIContent("Apply Recommended Hybrid Defaults", "Restores the snapshot, interpolation and transport values Netcode for GameObjects recommends for hybrid mode. Applied automatically once; use this to get back to them after changing them.")))
{
HybridNetcodeConfigApplier.ApplyDefaults(true);
}

GUILayout.EndVertical();
}

/// <summary>
/// Draws the opt-in for the experimental unified netcode API, writing the NGO hybrid mode defaults the first
/// time it is checked.
/// </summary>
/// <param name="settings">The project settings holding the opt-in flag.</param>
/// <returns>Whether the rest of the hybrid section should draw.</returns>
private static bool DrawUnifiedNetcodeApiToggle(NetcodeForGameObjectsProjectSettings settings)
{
const string enableUnifiedApiString = "Enable the experimental unified netcode API";

if (s_HybridLabelWidth == 0)
{
s_HybridLabelWidth = Mathf.Min(k_MaxLabelWidth, EditorStyles.label.CalcSize(new GUIContent(enableUnifiedApiString)).x);
}

EditorGUIUtility.labelWidth = s_HybridLabelWidth;
var enabled = EditorGUILayout.Toggle(
new GUIContent(
enableUnifiedApiString,
"When enabled, Netcode for GameObjects writes the NetCodeConfig values it recommends for hybrid " +
"mode. Disabling it again hides these settings and leaves the NetCodeConfig as it is."),
settings.EnableUnifiedNetcodeApi,
GUILayout.Width(s_HybridLabelWidth + 20));
EditorGUIUtility.labelWidth = s_MaxLabelWidth;

if (enabled != settings.EnableUnifiedNetcodeApi)
{
settings.EnableUnifiedNetcodeApi = enabled;
settings.SaveSettings();
if (enabled)
{
HybridNetcodeConfigApplier.ApplyDefaults(false);
}
}

return enabled;
}
#endif
}

internal class NetcodeSettingsLabel : NetcodeGUISettings
Expand Down
Loading