-
Notifications
You must be signed in to change notification settings - Fork 461
feat: hybrid NetcodeConfig defaults #4144
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
Open
NoelStephensUnity
wants to merge
16
commits into
develop-3.x.x
Choose a base branch
from
feat/hybrid-netcodeconfig-defaults
base: develop-3.x.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
69a99a9
feat: auto-configure NetcodeConfig for hybrid mode
NoelStephensUnity 49e605e
style
NoelStephensUnity 3474768
fix and test
NoelStephensUnity 19556af
Merge develop-3.x.x into feat/hybrid-netcodeconfig-defaults
netcode-ci-service 2c82988
Merge develop-3.x.x into feat/hybrid-netcodeconfig-defaults
netcode-ci-service 0239d3b
Merge develop-3.x.x into feat/hybrid-netcodeconfig-defaults
netcode-ci-service 392159f
Merge develop-3.x.x into feat/hybrid-netcodeconfig-defaults
netcode-ci-service 9c7fb1a
refactor
NoelStephensUnity f12c046
Merge branch 'develop-3.x.x' into feat/hybrid-netcodeconfig-defaults
NoelStephensUnity edb3404
test
NoelStephensUnity a2894d2
update
NoelStephensUnity 1571094
Merge branch 'develop-3.x.x' into feat/hybrid-netcodeconfig-defaults
NoelStephensUnity c4fd5b2
style
NoelStephensUnity 54375dc
Merge branch 'develop-3.x.x' into feat/hybrid-netcodeconfig-defaults
NoelStephensUnity 2e3c48f
Merge remote-tracking branch 'origin/develop-3.x.x' into feat/hybrid-…
NoelStephensUnity 17d2cf1
chore: n4e 7.0.0 spellings, review feedback and snapshot size
NoelStephensUnity File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
com.unity.netcode.gameobjects/Editor/Configuration/HybridNetcodeConfigApplier.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| #if UNIFIED_NETCODE | ||
| using Unity.Netcode.Logging; | ||
| #if !UNIFIED_NETCODE_7_0_0 | ||
| using NetcodeConfig = Unity.NetCode.NetCodeConfig; | ||
| #endif | ||
| using UnityEditor; | ||
|
|
||
| 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> | ||
| internal static class HybridNetcodeConfigApplier | ||
| { | ||
| [InitializeOnLoadMethod] | ||
| private static void OnApplicationStart() | ||
| { | ||
| // N4E assigns NetcodeConfig.Global from its own [InitializeOnLoadMethod] and creates the asset when | ||
| // there is none. delayCall runs after those have completed, which is what makes Global reliable here | ||
| // without a lookup of our own. | ||
| 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 (!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 = NetcodeConfig.Global; | ||
| if (config == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (HybridNetcodeDefaults.ApplyRecommended(config, HybridNetcodeDefaults.DefaultTickRate)) | ||
|
EmandM marked this conversation as resolved.
|
||
| { | ||
| EditorUtility.SetDirty(config); | ||
| AssetDatabase.SaveAssetIfDirty(config); | ||
| new ContextualLogger(config).Info(new Context(LogLevel.Developer, $"Applied the hybrid mode defaults to '{config.name}'. These are tuned for Netcode for GameObjects and can be changed freely; they will not be re-applied automatically. Use Project Settings > Multiplayer > Netcode for GameObjects to restore them.").AddTag("Unified")); | ||
| } | ||
|
|
||
| // 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(); | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
2 changes: 2 additions & 0 deletions
2
com.unity.netcode.gameobjects/Editor/Configuration/HybridNetcodeConfigApplier.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
com.unity.netcode.gameobjects/Runtime/Configuration/HybridNetcodeDefaults.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| #if UNIFIED_NETCODE | ||
| #if !UNIFIED_NETCODE_7_0_0 | ||
| using Unity.NetCode; | ||
| using NetcodeConfig = Unity.NetCode.NetCodeConfig; | ||
| #endif | ||
|
|
||
| namespace Unity.Netcode | ||
| { | ||
| /// <summary> | ||
| /// The <see cref="NetcodeConfig"/> values NGO needs when running in hybrid mode (i.e. Netcode for Entities is | ||
| /// installed and a registered network prefab carries a <see cref="GhostObject"/>). | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This lives in the runtime assembly rather than the editor one because <see cref="NetcodeConfig.HostWorldModeSelection"/> | ||
| /// is internal to Netcode for Entities, and Unity.Netcode.Runtime is the only NGO assembly it grants InternalsVisibleTo to. | ||
| /// Nothing here touches the AssetDatabase; the editor-side applier drives all of it. | ||
| /// </remarks> | ||
| internal static class HybridNetcodeDefaults | ||
| { | ||
| /// <summary> | ||
| /// Bump whenever <see cref="ApplyRecommended"/> changes so that an upgrading project re-applies exactly once. | ||
| /// Persisted as NetcodeForGameObjectsProjectSettings.HybridDefaultsVersion. | ||
| /// </summary> | ||
| internal const int Version = 2; | ||
|
|
||
| // Mirrors NetworkConfig.TickRate's default. The editor writes the defaults before any NetworkManager is | ||
| // necessarily loaded, so it has nothing to read the real rate from. NetworkManager re-aligns the config when a | ||
| // session carrying ghost prefabs starts, which is what makes writing a fixed value here safe. | ||
| internal const uint DefaultTickRate = 30; | ||
|
|
||
| // Tuned against 2000 GenericPhysicsBallNGO instances in the ngo-examples project. A hybrid ghost costs ~4.87 | ||
| // bytes per snapshot, so 4096 carries ~840 of them at the full tick rate. This is a cap and not a cost: | ||
| // below that count it puts no more on the wire than the N4E default would. Kept small because a snapshot is | ||
| // sent unreliably: losing any one of its fragments loses the whole snapshot. | ||
| internal const int SnapshotPacketSize = 4096; | ||
|
|
||
| // A ceiling on despawn bytes, not a reservation, so unused headroom is free. 0.2 is also N4E's clamp minimum. | ||
| internal const float PercentReservedForDespawn = 0.2f; | ||
|
|
||
| // Expressed in milliseconds rather than net ticks deliberately. N4E rounds this up to whole network ticks, so | ||
| // it holds >= 50ms of interpolation buffer at any tick rate. The net-tick form does not: 2 net ticks is 66.7ms | ||
| // at 30Hz but only 33.3ms at 60Hz, and 33.3ms is the buffer the stress test stuttered at. | ||
| internal const uint InterpolationTimeMS = 50; | ||
|
|
||
| internal const float InterpolationDelayMaxDeltaTicksFraction = 0.15f; | ||
| internal const float InterpolationTimeScaleMin = 0.9f; | ||
| internal const float InterpolationTimeScaleMax = 1.33f; | ||
|
|
||
| // A full snapshot fragments into ~3 datagrams and each fragment consumes a queue slot. | ||
| internal const int ClientQueueCapacity = 128; | ||
|
|
||
| /// <summary> | ||
| /// Applies the two settings hybrid mode cannot run without. | ||
| /// </summary> | ||
| /// <param name="config">The config to correct.</param> | ||
| /// <returns>True if anything changed.</returns> | ||
| internal static bool ApplyRequired(NetcodeConfig config) | ||
| { | ||
| var changed = false; | ||
|
|
||
| // NetworkManager gates the world spin-up, so N4E must not bootstrap worlds on its own. | ||
| if (config.EnableClientServerBootstrap != NetcodeConfig.AutomaticBootstrapSetting.DisableAutomaticBootstrap) | ||
| { | ||
| config.EnableClientServerBootstrap = NetcodeConfig.AutomaticBootstrapSetting.DisableAutomaticBootstrap; | ||
| changed = true; | ||
| } | ||
|
|
||
| if (config.HostWorldModeSelection != NetcodeConfig.HostWorldMode.SingleWorld) | ||
| { | ||
| config.HostWorldModeSelection = NetcodeConfig.HostWorldMode.SingleWorld; | ||
| changed = true; | ||
| } | ||
|
|
||
| return changed; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Drives N4E's tick rates from <see cref="NetworkConfig.TickRate"/> so that ghost transform updates land on | ||
| /// the same interval NGO uses for everything else. | ||
| /// </summary> | ||
| /// <param name="config">The config to correct.</param> | ||
| /// <param name="tickRate">The owning <see cref="NetworkManager"/>'s configured tick rate.</param> | ||
| /// <returns>True if anything changed.</returns> | ||
| internal static bool ApplyTickRate(NetcodeConfig config, uint tickRate) | ||
| { | ||
| var rate = (int)tickRate; | ||
| if (config.ClientServerTickRate.SimulationTickRate == rate && config.ClientServerTickRate.NetworkTickRate == rate) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // Both are written: leaving NetworkTickRate at 0 would track SimulationTickRate anyway, but writing it | ||
| // keeps the two visibly locked in the inspector, which is the invariant InterpolationTimeMS relies on. | ||
| config.ClientServerTickRate.SimulationTickRate = rate; | ||
| config.ClientServerTickRate.NetworkTickRate = rate; | ||
| return true; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Applies the full NGO-recommended set: <see cref="ApplyRequired"/>, <see cref="ApplyTickRate"/>, and the | ||
| /// values tuned against the stress test. | ||
| /// </summary> | ||
| /// <param name="config">The config to correct.</param> | ||
| /// <param name="tickRate">The owning <see cref="NetworkManager"/>'s configured tick rate.</param> | ||
| /// <returns>True if anything changed.</returns> | ||
| internal static bool ApplyRecommended(NetcodeConfig config, uint tickRate) | ||
| { | ||
| var changed = ApplyRequired(config); | ||
| changed |= ApplyTickRate(config, tickRate); | ||
|
|
||
| changed |= Set(ref config.GhostSendSystemData.DefaultSnapshotPacketSize, SnapshotPacketSize); | ||
| changed |= Set(ref config.GhostSendSystemData.PercentReservedForDespawnMessages, PercentReservedForDespawn); | ||
|
|
||
| // The net-tick form has to be cleared or it wins over the millisecond form. | ||
| changed |= Set(ref config.ClientTickRate.InterpolationTimeNetTicks, 0u); | ||
| changed |= Set(ref config.ClientTickRate.InterpolationTimeMS, InterpolationTimeMS); | ||
| changed |= Set(ref config.ClientTickRate.InterpolationDelayMaxDeltaTicksFraction, InterpolationDelayMaxDeltaTicksFraction); | ||
| changed |= Set(ref config.ClientTickRate.InterpolationTimeScaleMin, InterpolationTimeScaleMin); | ||
| changed |= Set(ref config.ClientTickRate.InterpolationTimeScaleMax, InterpolationTimeScaleMax); | ||
|
|
||
| changed |= Set(ref config.ClientSendQueueCapacity, ClientQueueCapacity); | ||
| changed |= Set(ref config.ClientReceiveQueueCapacity, ClientQueueCapacity); | ||
|
|
||
| return changed; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Reports the first required setting that is still wrong, for the runtime start-up check. | ||
| /// </summary> | ||
| /// <param name="config">The config to inspect.</param> | ||
| /// <param name="reason">Populated with a user-facing description of what is wrong.</param> | ||
| /// <returns>True when <paramref name="config"/> cannot support hybrid mode as-is.</returns> | ||
| internal static bool IsMissingRequired(NetcodeConfig config, out string reason) | ||
| { | ||
| if (config.HostWorldModeSelection != NetcodeConfig.HostWorldMode.SingleWorld) | ||
| { | ||
| reason = $"{nameof(NetcodeConfig.HostWorldModeSelection)} must be {nameof(NetcodeConfig.HostWorldMode.SingleWorld)} but is {config.HostWorldModeSelection}"; | ||
| return true; | ||
| } | ||
|
|
||
| if (config.EnableClientServerBootstrap != NetcodeConfig.AutomaticBootstrapSetting.DisableAutomaticBootstrap) | ||
| { | ||
| reason = $"{nameof(NetcodeConfig.EnableClientServerBootstrap)} must be {nameof(NetcodeConfig.AutomaticBootstrapSetting.DisableAutomaticBootstrap)} because {nameof(NetworkManager)} owns world creation in hybrid mode"; | ||
| return true; | ||
| } | ||
|
|
||
| reason = null; | ||
| return false; | ||
| } | ||
|
|
||
| private static bool Set<T>(ref T target, T value) | ||
| where T : System.IEquatable<T> | ||
| { | ||
| if (target.Equals(value)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| target = value; | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| #endif |
2 changes: 2 additions & 0 deletions
2
com.unity.netcode.gameobjects/Runtime/Configuration/HybridNetcodeDefaults.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.