From 9804c9b83eb74224ef5917e65aa1193733cf5c7e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 19 Feb 2025 13:26:05 -0600 Subject: [PATCH 01/11] update Adding NetworkManagerAnalytics struct --- .../Analytics/NetworkManagerAnalytics.cs | 62 +++++++++++++++++++ .../Analytics/NetworkManagerAnalytics.cs.meta | 2 + 2 files changed, 64 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs new file mode 100644 index 0000000000..4cc671537a --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs @@ -0,0 +1,62 @@ +#if UNITY_EDITOR +using System.Text; +using UnityEngine; + +namespace Unity.Netcode +{ + internal struct NetworkManagerAnalytics + { + public bool IsUsingMultiplayerSDK; + public bool UsedCMBService; + public string NetworkTopology; + public string NetworkTransport; + public bool PlayerPrefabSet; + public bool ConnectionApproval; + public float ClientConnectionBufferTimeout; + public bool EnsureNetworkVariableLengthSafety; + public bool EnableSceneManagement; + public float LoadSceneTimeOut; + public float SpawnTimeout; + public bool ForceSamePrefabs; + public bool RecycleNetworkIds; + public float NetworkIdRecycleDelay; + public int RpcHashSize; + public bool EnableTimeResync; + public float TimeResyncInterval; + public int TickRate; + public bool IsUsingMultiplayerTools; + public bool NetworkMessageMetrics; + public bool NetworkProfilingMetrics; + public bool WasServer; + public bool WasClient; + public float SessionDuration; + internal void LogAnalytics(int sessionNumber) + { + var message = new StringBuilder(); + message.AppendLine($"{nameof(NetworkManagerAnalytics)}-{sessionNumber} Session Duration: {SessionDuration} Sever: {WasServer} Client: {WasClient}"); + message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}"); + message.AppendLine($"{nameof(UsedCMBService)}: {UsedCMBService}"); + message.AppendLine($"{nameof(NetworkTopology)}: {NetworkTopology}"); + message.AppendLine($"{nameof(NetworkTransport)}: {NetworkTransport}"); + message.AppendLine($"{nameof(PlayerPrefabSet)}: {PlayerPrefabSet}"); + message.AppendLine($"{nameof(ConnectionApproval)}: {ConnectionApproval}"); + message.AppendLine($"{nameof(ClientConnectionBufferTimeout)}: {ClientConnectionBufferTimeout}"); + message.AppendLine($"{nameof(EnsureNetworkVariableLengthSafety)}: {EnsureNetworkVariableLengthSafety}"); + message.AppendLine($"{nameof(EnableSceneManagement)}: {EnableSceneManagement}"); + message.AppendLine($"{nameof(LoadSceneTimeOut)}: {LoadSceneTimeOut}"); + message.AppendLine($"{nameof(SpawnTimeout)}: {SpawnTimeout}"); + message.AppendLine($"{nameof(ForceSamePrefabs)}: {ForceSamePrefabs}"); + message.AppendLine($"{nameof(RecycleNetworkIds)}: {RecycleNetworkIds}"); + message.AppendLine($"{nameof(NetworkIdRecycleDelay)}: {NetworkIdRecycleDelay}"); + message.AppendLine($"{nameof(RpcHashSize)}: {RpcHashSize}"); + message.AppendLine($"{nameof(EnableTimeResync)}: {EnableTimeResync}"); + message.AppendLine($"{nameof(TimeResyncInterval)}: {TimeResyncInterval}"); + message.AppendLine($"{nameof(TickRate)}: {TickRate}"); + message.AppendLine($"{nameof(IsUsingMultiplayerTools)}: {IsUsingMultiplayerTools}"); + message.AppendLine($"{nameof(NetworkMessageMetrics)}: {NetworkMessageMetrics}"); + message.AppendLine($"{nameof(NetworkProfilingMetrics)}: {NetworkProfilingMetrics}"); + Debug.Log($"{message}"); + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta new file mode 100644 index 0000000000..10980f86d5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a987d18ba709af44bbe4033d29b80cb6 \ No newline at end of file From a17194f05e20cd5d9fd99784546ce304d43ed306 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 19 Feb 2025 13:28:58 -0600 Subject: [PATCH 02/11] update Adding analytics information gathering scripts to handle tracking the network configurations for each session while in play mode. Upon exiting play mode, analytics data can be submitted. The actual submission is yet to be implemented. --- .../Editor/NetworkManagerHelper.cs | 68 ++++++++++++++ .../Runtime/Configuration/NetworkConfig.cs | 42 +++++++++ .../Runtime/Core/NetworkManager.cs | 89 ++++++++++++++++++- 3 files changed, 197 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 3138369d57..5fa1467b11 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -3,6 +3,7 @@ using Unity.Netcode.Editor.Configuration; using UnityEditor; using UnityEngine; +using UnityEngine.Analytics; using UnityEngine.SceneManagement; namespace Unity.Netcode.Editor @@ -224,6 +225,73 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool } return isParented; } + + private const bool k_EnableAnalyticsLogging = true; + + public void UpdateAnalytics() + { + if (!EditorAnalytics.enabled) + { + return; + } + for (int i = 0; i < NetworkManager.RecentSessions.Count; i++) + { + var networkManagerAnalytics = GetNetworkManagerAnalytics(NetworkManager.RecentSessions[i]); + if (k_EnableAnalyticsLogging) + { + networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); + } + } + } + + + private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkManager.NetworkSessionInfo networkSession) + { + var multiplayerSDKInstalled = false; + var multiplayerToolsInstalled = false; +#if MULTIPLAYER_SERVICES_SDK_INSTALLED + multiplayerSDKInstalled = true; +#endif +#if MULTIPLAYER_TOOLS + multiplayerToolsInstalled = true; +#endif + if (!networkSession.SessionStopped) + { + Debug.LogWarning($"Session-{networkSession.SessionIndex} was not considered stopped!"); + } + var networkManagerAnalytics = new NetworkManagerAnalytics() + { + NetworkTopology = networkSession.NetworkConfig.NetworkTopology.ToString(), + UsedCMBService = networkSession.UsedCMBService, + NetworkTransport = networkSession.Transport, + IsUsingMultiplayerSDK = multiplayerSDKInstalled, + IsUsingMultiplayerTools = multiplayerToolsInstalled, + PlayerPrefabSet = networkSession.PlayerPrefab, + ConnectionApproval = networkSession.NetworkConfig.ConnectionApproval, + ClientConnectionBufferTimeout = networkSession.NetworkConfig.ClientConnectionBufferTimeout, + EnsureNetworkVariableLengthSafety = networkSession.NetworkConfig.EnsureNetworkVariableLengthSafety, + EnableSceneManagement = networkSession.NetworkConfig.EnableSceneManagement, + LoadSceneTimeOut = networkSession.NetworkConfig.LoadSceneTimeOut, + SpawnTimeout = networkSession.NetworkConfig.SpawnTimeout, + ForceSamePrefabs = networkSession.NetworkConfig.ForceSamePrefabs, + RecycleNetworkIds = networkSession.NetworkConfig.RecycleNetworkIds, + NetworkIdRecycleDelay = networkSession.NetworkConfig.NetworkIdRecycleDelay, + RpcHashSize = networkSession.NetworkConfig.RpcHashSize == HashSize.VarIntFourBytes ? 4 : 8, + EnableTimeResync = networkSession.NetworkConfig.EnableTimeResync, + TimeResyncInterval = networkSession.NetworkConfig.TimeResyncInterval, + TickRate = (int)networkSession.NetworkConfig.TickRate, +#if MULTIPLAYER_TOOLS + NetworkMessageMetrics = networkSession.NetworkConfig.NetworkMessageMetrics, +#else + NetworkMessageMetrics = false, +#endif + NetworkProfilingMetrics = networkSession.NetworkConfig.NetworkProfilingMetrics, + WasClient = networkSession.WasClient, + WasServer = networkSession.WasServer, + SessionDuration = networkSession.SessionEnd - networkSession.SessionStart, + }; + return networkManagerAnalytics; + } } #endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index fbbbf86ebb..fda8ceb338 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -168,6 +168,48 @@ public class NetworkConfig [Tooltip("When enabled (default), the player prefab will automatically be spawned (client-side) upon the client being approved and synchronized.")] public bool AutoSpawnPlayerPrefabClientSide = true; +#if UNITY_EDITOR + /// + /// Creates a copy of the current + /// + /// a copy of this + internal NetworkConfig Copy() + { + var networkConfig = new NetworkConfig() + { + ProtocolVersion = ProtocolVersion, + NetworkTransport = NetworkTransport, + TickRate = TickRate, + ClientConnectionBufferTimeout = ClientConnectionBufferTimeout, + ConnectionApproval = ConnectionApproval, + EnableTimeResync = EnableTimeResync, + TimeResyncInterval = TimeResyncInterval, + EnsureNetworkVariableLengthSafety = EnsureNetworkVariableLengthSafety, + EnableSceneManagement = EnableSceneManagement, + ForceSamePrefabs = ForceSamePrefabs, + RecycleNetworkIds = RecycleNetworkIds, + NetworkIdRecycleDelay = NetworkIdRecycleDelay, + RpcHashSize = RpcHashSize, + LoadSceneTimeOut = LoadSceneTimeOut, + SpawnTimeout = SpawnTimeout, + EnableNetworkLogs = EnableNetworkLogs, + NetworkTopology = NetworkTopology, + UseCMBService = UseCMBService, + AutoSpawnPlayerPrefabClientSide = AutoSpawnPlayerPrefabClientSide, +#if MULTIPLAYER_TOOLS + NetworkMessageMetrics = NetworkMessageMetrics, +#else + NetworkMessageMetrics = false, +#endif + NetworkProfilingMetrics = NetworkProfilingMetrics, + }; + + return networkConfig; + } + +#endif + + #if MULTIPLAYER_TOOLS /// /// Controls whether network messaging metrics will be gathered. (defaults to true) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 9f257fb5e1..ed3959a37d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -914,6 +914,22 @@ internal T Value #if UNITY_EDITOR internal static INetworkManagerHelper NetworkManagerHelper; + internal struct NetworkSessionInfo + { + public int SessionIndex; + public bool SessionStopped; + public bool PlayerPrefab; + public bool WasServer; + public bool WasClient; + public float SessionStart; + public float SessionEnd; + public bool UsedCMBService; + public string Transport; + public NetworkConfig NetworkConfig; + } + + internal static List RecentSessions = new List(); + /// /// Interface for NetworkManagerHelper /// @@ -921,6 +937,8 @@ internal interface INetworkManagerHelper { bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false); void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false); + + void UpdateAnalytics(); } internal delegate void ResetNetworkManagerDelegate(NetworkManager manager); @@ -1018,9 +1036,69 @@ internal void OnValidate() private void ModeChanged(PlayModeStateChange change) { - if (IsListening && change == PlayModeStateChange.ExitingPlayMode) + if (change == PlayModeStateChange.EnteredPlayMode) { - OnApplicationQuit(); + RecentSessions.Clear(); + } + + if (change == PlayModeStateChange.ExitingPlayMode) + { + try + { + EndNetworkSession(); + NetworkManagerHelper?.UpdateAnalytics(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + RecentSessions.Clear(); + + if (IsListening) + { + OnApplicationQuit(); + } + } + } + + private void BeginNetworkSession() + { + if (!EditorAnalytics.enabled) + { + return; + } + var newSession = new NetworkSessionInfo() + { + SessionIndex = RecentSessions.Count, + WasClient = IsClient, + WasServer = IsServer, + SessionStart = Time.realtimeSinceStartup, + NetworkConfig = NetworkConfig.Copy(), + PlayerPrefab = NetworkConfig.PlayerPrefab != null, + Transport = NetworkConfig.NetworkTransport != null ? NetworkConfig.NetworkTransport.GetType().Name : "None", + }; + RecentSessions.Add(newSession); + } + + private void EndNetworkSession() + { + // If analytics is disabled, then exit early + if (!EditorAnalytics.enabled) + { + return; + } + if (RecentSessions.Count > 0) + { + var lastIndex = RecentSessions.Count - 1; + var recentSession = RecentSessions[lastIndex]; + if (recentSession.SessionStopped) + { + return; + } + recentSession.SessionEnd = Time.realtimeSinceStartup; + recentSession.UsedCMBService = CMBServiceConnection; + recentSession.SessionStopped = true; + RecentSessions[lastIndex] = recentSession; } } #endif @@ -1285,6 +1363,9 @@ internal void Initialize(bool server) NetworkConfig.InitializePrefabs(); PrefabHandler.RegisterPlayerPrefab(); +#if UNITY_EDITOR + BeginNetworkSession(); +#endif } private enum StartType @@ -1584,6 +1665,10 @@ private void OnSceneUnloaded(Scene scene) internal void ShutdownInternal() { +#if UNITY_EDITOR + EndNetworkSession(); +#endif + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { NetworkLog.LogInfo(nameof(ShutdownInternal)); From 063e5c880ac4efc65bb8ddc2e08a4c4abd7d5725 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 19 Feb 2025 18:36:37 -0600 Subject: [PATCH 03/11] update Finalizing the first round pass of gathering analytics. --- .../Editor/Analytics.meta | 8 ++++++ .../Editor/Analytics/AnalyticsHandler.cs | 23 ++++++++++++++++ .../Editor/Analytics/AnalyticsHandler.cs.meta | 2 ++ .../Analytics/NetworkManagerAnalytics.cs | 26 +++++++++++++------ .../NetworkManagerAnalyticsHandler.cs | 12 +++++++++ .../NetworkManagerAnalyticsHandler.cs.meta | 2 ++ .../Editor/NetworkManagerHelper.cs | 20 ++++++++++++-- .../Runtime/Core/NetworkManager.cs | 13 ++++++++++ 8 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/Analytics.meta b/com.unity.netcode.gameobjects/Editor/Analytics.meta new file mode 100644 index 0000000000..8894158a2b --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: df502b16f2458f1458f8546326dc5ef1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs new file mode 100644 index 0000000000..e16dc23218 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs @@ -0,0 +1,23 @@ +#if UNITY_EDITOR +using System; +using UnityEngine.Analytics; + +namespace Unity.Netcode.Editor +{ + internal class AnalyticsHandler : IAnalytic where T : IAnalytic.IData + { + private T m_Data; + + public AnalyticsHandler(T data) + { + m_Data = data; + } + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + data = m_Data; + error = null; + return data != null; + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta new file mode 100644 index 0000000000..e1dbb20b47 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3f65a17a86eb08c42be3b01b67fb6781 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs index 4cc671537a..6f8363ea75 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs @@ -1,10 +1,13 @@ #if UNITY_EDITOR +using System; using System.Text; using UnityEngine; +using UnityEngine.Analytics; -namespace Unity.Netcode +namespace Unity.Netcode.Editor { - internal struct NetworkManagerAnalytics + [Serializable] + internal struct NetworkManagerAnalytics : IAnalytic.IData { public bool IsUsingMultiplayerSDK; public bool UsedCMBService; @@ -12,17 +15,17 @@ internal struct NetworkManagerAnalytics public string NetworkTransport; public bool PlayerPrefabSet; public bool ConnectionApproval; - public float ClientConnectionBufferTimeout; + public int ClientConnectionBufferTimeout; public bool EnsureNetworkVariableLengthSafety; public bool EnableSceneManagement; - public float LoadSceneTimeOut; + public int LoadSceneTimeOut; public float SpawnTimeout; public bool ForceSamePrefabs; public bool RecycleNetworkIds; public float NetworkIdRecycleDelay; public int RpcHashSize; public bool EnableTimeResync; - public float TimeResyncInterval; + public int TimeResyncInterval; public int TickRate; public bool IsUsingMultiplayerTools; public bool NetworkMessageMetrics; @@ -30,10 +33,13 @@ internal struct NetworkManagerAnalytics public bool WasServer; public bool WasClient; public float SessionDuration; - internal void LogAnalytics(int sessionNumber) + + public override string ToString() { var message = new StringBuilder(); - message.AppendLine($"{nameof(NetworkManagerAnalytics)}-{sessionNumber} Session Duration: {SessionDuration} Sever: {WasServer} Client: {WasClient}"); + message.AppendLine($"{nameof(WasServer)}: {WasServer}"); + message.AppendLine($"{nameof(WasClient)}: {WasClient}"); + message.AppendLine($"{nameof(SessionDuration)}: {SessionDuration}"); message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}"); message.AppendLine($"{nameof(UsedCMBService)}: {UsedCMBService}"); message.AppendLine($"{nameof(NetworkTopology)}: {NetworkTopology}"); @@ -55,7 +61,11 @@ internal void LogAnalytics(int sessionNumber) message.AppendLine($"{nameof(IsUsingMultiplayerTools)}: {IsUsingMultiplayerTools}"); message.AppendLine($"{nameof(NetworkMessageMetrics)}: {NetworkMessageMetrics}"); message.AppendLine($"{nameof(NetworkProfilingMetrics)}: {NetworkProfilingMetrics}"); - Debug.Log($"{message}"); + return message.ToString(); + } + internal void LogAnalytics(int sessionNumber) + { + Debug.Log($"{ToString()}"); } } } diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs new file mode 100644 index 0000000000..72cf84aa69 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs @@ -0,0 +1,12 @@ +#if UNITY_EDITOR +using UnityEngine.Analytics; + +namespace Unity.Netcode.Editor +{ + [AnalyticInfo("NGO_NetworkManager", "unity.netcode", 3, 100, 1000)] + internal class NetworkManagerAnalyticsHandler : AnalyticsHandler + { + public NetworkManagerAnalyticsHandler(NetworkManagerAnalytics networkManagerAnalytics) : base(networkManagerAnalytics) { } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta new file mode 100644 index 0000000000..a088b0bb93 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 087c955a14fef5448bcd1f7c7a95b21f \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 5fa1467b11..1f8351c760 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -227,13 +227,18 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool } private const bool k_EnableAnalyticsLogging = true; - + /// + /// Invoked from within when exiting play mode. + /// public void UpdateAnalytics() { + // Exit early if analytics is disabled if (!EditorAnalytics.enabled) { return; } + + // Parse through all of the recent network sessions to generate and send NetworkManager analytics for (int i = 0; i < NetworkManager.RecentSessions.Count; i++) { var networkManagerAnalytics = GetNetworkManagerAnalytics(NetworkManager.RecentSessions[i]); @@ -241,10 +246,21 @@ public void UpdateAnalytics() { networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); } + + var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); + + if (result != AnalyticsResult.Ok) + { + Debug.LogWarning($"[Analytics] Problem sending analytics: {result}"); + } } } - + /// + /// Generates a based on the passed in + /// + /// Represents a network session with the used NetworkManager configuration + /// private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkManager.NetworkSessionInfo networkSession) { var multiplayerSDKInstalled = false; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index ed3959a37d..038b16e026 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1061,6 +1061,12 @@ private void ModeChanged(PlayModeStateChange change) } } + /// + /// If analytics is enabled, this will create a new struct. + /// + /// + /// Invoked when NetworkManager is started. + /// private void BeginNetworkSession() { if (!EditorAnalytics.enabled) @@ -1080,6 +1086,12 @@ private void BeginNetworkSession() RecentSessions.Add(newSession); } + /// + /// If analytics is enabled, this will finalize the current struct. + /// + /// + /// Invoked when NetworkManager is stopped or upon exiting play mode. + /// private void EndNetworkSession() { // If analytics is disabled, then exit early @@ -1091,6 +1103,7 @@ private void EndNetworkSession() { var lastIndex = RecentSessions.Count - 1; var recentSession = RecentSessions[lastIndex]; + // If the session has already been finalized, then exit early. if (recentSession.SessionStopped) { return; From 0edb4d678aa3cc791ac2e32a4222d17bfdfdc32a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 19 Feb 2025 19:43:10 -0600 Subject: [PATCH 04/11] update Removing the erroneous NetworkMessageMetrics when MULTIPLAYER_TOOLS was not defined from NetworkConfig. Remove unused namespace and minor clean up. --- .../Editor/NetworkManagerHelper.cs | 21 +++++++------------ .../Runtime/Configuration/NetworkConfig.cs | 2 -- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 1f8351c760..18e70392d1 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -3,7 +3,6 @@ using Unity.Netcode.Editor.Configuration; using UnityEditor; using UnityEngine; -using UnityEngine.Analytics; using UnityEngine.SceneManagement; namespace Unity.Netcode.Editor @@ -226,7 +225,6 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool return isParented; } - private const bool k_EnableAnalyticsLogging = true; /// /// Invoked from within when exiting play mode. /// @@ -242,17 +240,16 @@ public void UpdateAnalytics() for (int i = 0; i < NetworkManager.RecentSessions.Count; i++) { var networkManagerAnalytics = GetNetworkManagerAnalytics(NetworkManager.RecentSessions[i]); - if (k_EnableAnalyticsLogging) - { - networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); - } - +#if ENABLE_NGO_ANALYTICS_LOGGING + networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); +#endif var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); - +#if ENABLE_NGO_ANALYTICS_LOGGING if (result != AnalyticsResult.Ok) { Debug.LogWarning($"[Analytics] Problem sending analytics: {result}"); } +#endif } } @@ -265,11 +262,13 @@ private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkManager.Networ { var multiplayerSDKInstalled = false; var multiplayerToolsInstalled = false; + var networkMessageMetrics = false; #if MULTIPLAYER_SERVICES_SDK_INSTALLED multiplayerSDKInstalled = true; #endif #if MULTIPLAYER_TOOLS multiplayerToolsInstalled = true; + networkMessageMetrics = networkSession.NetworkConfig.NetworkMessageMetrics; #endif if (!networkSession.SessionStopped) { @@ -296,11 +295,7 @@ private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkManager.Networ EnableTimeResync = networkSession.NetworkConfig.EnableTimeResync, TimeResyncInterval = networkSession.NetworkConfig.TimeResyncInterval, TickRate = (int)networkSession.NetworkConfig.TickRate, -#if MULTIPLAYER_TOOLS - NetworkMessageMetrics = networkSession.NetworkConfig.NetworkMessageMetrics, -#else - NetworkMessageMetrics = false, -#endif + NetworkMessageMetrics = networkMessageMetrics, NetworkProfilingMetrics = networkSession.NetworkConfig.NetworkProfilingMetrics, WasClient = networkSession.WasClient, WasServer = networkSession.WasServer, diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index fda8ceb338..826fb15ecf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -198,8 +198,6 @@ internal NetworkConfig Copy() AutoSpawnPlayerPrefabClientSide = AutoSpawnPlayerPrefabClientSide, #if MULTIPLAYER_TOOLS NetworkMessageMetrics = NetworkMessageMetrics, -#else - NetworkMessageMetrics = false, #endif NetworkProfilingMetrics = NetworkProfilingMetrics, }; From 27a91af6f639c50220bbf611cde4e59065148a00 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 24 Feb 2025 09:24:13 -0600 Subject: [PATCH 05/11] update Adjusting to final first pass analytics data being gathered. --- .../Analytics/NetworkManagerAnalytics.cs | 57 ++++++------------- .../NetworkManagerAnalyticsHandler.cs | 2 +- .../Editor/NetworkManagerHelper.cs | 43 ++++++-------- .../Runtime/Core/NetworkManager.cs | 6 -- 4 files changed, 33 insertions(+), 75 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs index 6f8363ea75..7321032154 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs @@ -7,66 +7,41 @@ namespace Unity.Netcode.Editor { [Serializable] - internal struct NetworkManagerAnalytics : IAnalytic.IData + internal struct NetworkManagerAnalytics : IAnalytic.IData, IEquatable { - public bool IsUsingMultiplayerSDK; + public bool IsDistributedAuthority; + public bool WasServer; + public bool WasClient; public bool UsedCMBService; - public string NetworkTopology; + public bool IsUsingMultiplayerSDK; public string NetworkTransport; - public bool PlayerPrefabSet; - public bool ConnectionApproval; - public int ClientConnectionBufferTimeout; - public bool EnsureNetworkVariableLengthSafety; public bool EnableSceneManagement; - public int LoadSceneTimeOut; - public float SpawnTimeout; - public bool ForceSamePrefabs; - public bool RecycleNetworkIds; - public float NetworkIdRecycleDelay; - public int RpcHashSize; - public bool EnableTimeResync; - public int TimeResyncInterval; public int TickRate; - public bool IsUsingMultiplayerTools; - public bool NetworkMessageMetrics; - public bool NetworkProfilingMetrics; - public bool WasServer; - public bool WasClient; - public float SessionDuration; - public override string ToString() { var message = new StringBuilder(); + message.AppendLine($"{nameof(IsDistributedAuthority)}: {IsDistributedAuthority}"); message.AppendLine($"{nameof(WasServer)}: {WasServer}"); - message.AppendLine($"{nameof(WasClient)}: {WasClient}"); - message.AppendLine($"{nameof(SessionDuration)}: {SessionDuration}"); - message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}"); + message.AppendLine($"{nameof(WasClient)}: {WasClient}"); message.AppendLine($"{nameof(UsedCMBService)}: {UsedCMBService}"); - message.AppendLine($"{nameof(NetworkTopology)}: {NetworkTopology}"); + message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}"); message.AppendLine($"{nameof(NetworkTransport)}: {NetworkTransport}"); - message.AppendLine($"{nameof(PlayerPrefabSet)}: {PlayerPrefabSet}"); - message.AppendLine($"{nameof(ConnectionApproval)}: {ConnectionApproval}"); - message.AppendLine($"{nameof(ClientConnectionBufferTimeout)}: {ClientConnectionBufferTimeout}"); - message.AppendLine($"{nameof(EnsureNetworkVariableLengthSafety)}: {EnsureNetworkVariableLengthSafety}"); message.AppendLine($"{nameof(EnableSceneManagement)}: {EnableSceneManagement}"); - message.AppendLine($"{nameof(LoadSceneTimeOut)}: {LoadSceneTimeOut}"); - message.AppendLine($"{nameof(SpawnTimeout)}: {SpawnTimeout}"); - message.AppendLine($"{nameof(ForceSamePrefabs)}: {ForceSamePrefabs}"); - message.AppendLine($"{nameof(RecycleNetworkIds)}: {RecycleNetworkIds}"); - message.AppendLine($"{nameof(NetworkIdRecycleDelay)}: {NetworkIdRecycleDelay}"); - message.AppendLine($"{nameof(RpcHashSize)}: {RpcHashSize}"); - message.AppendLine($"{nameof(EnableTimeResync)}: {EnableTimeResync}"); - message.AppendLine($"{nameof(TimeResyncInterval)}: {TimeResyncInterval}"); message.AppendLine($"{nameof(TickRate)}: {TickRate}"); - message.AppendLine($"{nameof(IsUsingMultiplayerTools)}: {IsUsingMultiplayerTools}"); - message.AppendLine($"{nameof(NetworkMessageMetrics)}: {NetworkMessageMetrics}"); - message.AppendLine($"{nameof(NetworkProfilingMetrics)}: {NetworkProfilingMetrics}"); return message.ToString(); } internal void LogAnalytics(int sessionNumber) { Debug.Log($"{ToString()}"); } + + public bool Equals(NetworkManagerAnalytics other) + { + return IsDistributedAuthority == other.IsDistributedAuthority && WasServer == other.WasServer && WasClient == other.WasClient + && UsedCMBService == other.UsedCMBService && IsUsingMultiplayerSDK == other.IsUsingMultiplayerSDK + && EnableSceneManagement == other.EnableSceneManagement && TickRate == other.TickRate + && NetworkTransport.Equals(other.NetworkTransport); + } } } #endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs index 72cf84aa69..7866df9621 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.Editor { - [AnalyticInfo("NGO_NetworkManager", "unity.netcode", 3, 100, 1000)] + [AnalyticInfo("NGO_NetworkManager", "unity.netcode", 5, 100, 1000)] internal class NetworkManagerAnalyticsHandler : AnalyticsHandler { public NetworkManagerAnalyticsHandler(NetworkManagerAnalytics networkManagerAnalytics) : base(networkManagerAnalytics) { } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 18e70392d1..01bedf3486 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -231,18 +231,25 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool public void UpdateAnalytics() { // Exit early if analytics is disabled - if (!EditorAnalytics.enabled) + if (!EditorAnalytics.enabled || NetworkManager.RecentSessions.Count == 0) { return; } + var previousAnalytics = new NetworkManagerAnalytics(); // Parse through all of the recent network sessions to generate and send NetworkManager analytics for (int i = 0; i < NetworkManager.RecentSessions.Count; i++) { var networkManagerAnalytics = GetNetworkManagerAnalytics(NetworkManager.RecentSessions[i]); + #if ENABLE_NGO_ANALYTICS_LOGGING networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); #endif + // If the previous session has no changes to the configuration then skip it (only unique configurations) + if (previousAnalytics.Equals(networkManagerAnalytics)) + { + continue; + } var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); #if ENABLE_NGO_ANALYTICS_LOGGING if (result != AnalyticsResult.Ok) @@ -250,6 +257,7 @@ public void UpdateAnalytics() Debug.LogWarning($"[Analytics] Problem sending analytics: {result}"); } #endif + previousAnalytics = networkManagerAnalytics; } } @@ -261,45 +269,26 @@ public void UpdateAnalytics() private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkManager.NetworkSessionInfo networkSession) { var multiplayerSDKInstalled = false; - var multiplayerToolsInstalled = false; - var networkMessageMetrics = false; #if MULTIPLAYER_SERVICES_SDK_INSTALLED multiplayerSDKInstalled = true; #endif -#if MULTIPLAYER_TOOLS - multiplayerToolsInstalled = true; - networkMessageMetrics = networkSession.NetworkConfig.NetworkMessageMetrics; -#endif +#if ENABLE_NGO_ANALYTICS_LOGGING if (!networkSession.SessionStopped) { Debug.LogWarning($"Session-{networkSession.SessionIndex} was not considered stopped!"); } +#endif + var networkManagerAnalytics = new NetworkManagerAnalytics() { - NetworkTopology = networkSession.NetworkConfig.NetworkTopology.ToString(), + IsDistributedAuthority = networkSession.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, + WasServer = networkSession.WasServer, + WasClient = networkSession.WasClient, UsedCMBService = networkSession.UsedCMBService, - NetworkTransport = networkSession.Transport, IsUsingMultiplayerSDK = multiplayerSDKInstalled, - IsUsingMultiplayerTools = multiplayerToolsInstalled, - PlayerPrefabSet = networkSession.PlayerPrefab, - ConnectionApproval = networkSession.NetworkConfig.ConnectionApproval, - ClientConnectionBufferTimeout = networkSession.NetworkConfig.ClientConnectionBufferTimeout, - EnsureNetworkVariableLengthSafety = networkSession.NetworkConfig.EnsureNetworkVariableLengthSafety, + NetworkTransport = networkSession.Transport, EnableSceneManagement = networkSession.NetworkConfig.EnableSceneManagement, - LoadSceneTimeOut = networkSession.NetworkConfig.LoadSceneTimeOut, - SpawnTimeout = networkSession.NetworkConfig.SpawnTimeout, - ForceSamePrefabs = networkSession.NetworkConfig.ForceSamePrefabs, - RecycleNetworkIds = networkSession.NetworkConfig.RecycleNetworkIds, - NetworkIdRecycleDelay = networkSession.NetworkConfig.NetworkIdRecycleDelay, - RpcHashSize = networkSession.NetworkConfig.RpcHashSize == HashSize.VarIntFourBytes ? 4 : 8, - EnableTimeResync = networkSession.NetworkConfig.EnableTimeResync, - TimeResyncInterval = networkSession.NetworkConfig.TimeResyncInterval, TickRate = (int)networkSession.NetworkConfig.TickRate, - NetworkMessageMetrics = networkMessageMetrics, - NetworkProfilingMetrics = networkSession.NetworkConfig.NetworkProfilingMetrics, - WasClient = networkSession.WasClient, - WasServer = networkSession.WasServer, - SessionDuration = networkSession.SessionEnd - networkSession.SessionStart, }; return networkManagerAnalytics; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 038b16e026..cd1b133874 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -918,11 +918,8 @@ internal struct NetworkSessionInfo { public int SessionIndex; public bool SessionStopped; - public bool PlayerPrefab; public bool WasServer; public bool WasClient; - public float SessionStart; - public float SessionEnd; public bool UsedCMBService; public string Transport; public NetworkConfig NetworkConfig; @@ -1078,9 +1075,7 @@ private void BeginNetworkSession() SessionIndex = RecentSessions.Count, WasClient = IsClient, WasServer = IsServer, - SessionStart = Time.realtimeSinceStartup, NetworkConfig = NetworkConfig.Copy(), - PlayerPrefab = NetworkConfig.PlayerPrefab != null, Transport = NetworkConfig.NetworkTransport != null ? NetworkConfig.NetworkTransport.GetType().Name : "None", }; RecentSessions.Add(newSession); @@ -1108,7 +1103,6 @@ private void EndNetworkSession() { return; } - recentSession.SessionEnd = Time.realtimeSinceStartup; recentSession.UsedCMBService = CMBServiceConnection; recentSession.SessionStopped = true; RecentSessions[lastIndex] = recentSession; From 2287c0506aaf7d058951ecfabe48219c5a4c7df7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 24 Feb 2025 10:10:55 -0600 Subject: [PATCH 06/11] style Removing white spaces. Excluding the LogAnalytics method unless ENABLE_NGO_ANALYTICS_LOGGING is defined. --- .../Editor/Analytics/NetworkManagerAnalytics.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs index 7321032154..6c49379f52 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs @@ -1,7 +1,6 @@ #if UNITY_EDITOR using System; using System.Text; -using UnityEngine; using UnityEngine.Analytics; namespace Unity.Netcode.Editor @@ -22,7 +21,7 @@ public override string ToString() var message = new StringBuilder(); message.AppendLine($"{nameof(IsDistributedAuthority)}: {IsDistributedAuthority}"); message.AppendLine($"{nameof(WasServer)}: {WasServer}"); - message.AppendLine($"{nameof(WasClient)}: {WasClient}"); + message.AppendLine($"{nameof(WasClient)}: {WasClient}"); message.AppendLine($"{nameof(UsedCMBService)}: {UsedCMBService}"); message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}"); message.AppendLine($"{nameof(NetworkTransport)}: {NetworkTransport}"); @@ -30,11 +29,12 @@ public override string ToString() message.AppendLine($"{nameof(TickRate)}: {TickRate}"); return message.ToString(); } +#if ENABLE_NGO_ANALYTICS_LOGGING internal void LogAnalytics(int sessionNumber) { Debug.Log($"{ToString()}"); } - +#endif public bool Equals(NetworkManagerAnalytics other) { return IsDistributedAuthority == other.IsDistributedAuthority && WasServer == other.WasServer && WasClient == other.WasClient From 92a63851299ed145df2c5ea543f5b2804d42018e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 24 Feb 2025 10:21:59 -0600 Subject: [PATCH 07/11] update Minor adjustments for when ENABLE_NGO_ANALYTICS_LOGGING is defined. --- .../Editor/Analytics/NetworkManagerAnalytics.cs | 5 ++++- .../Editor/NetworkManagerHelper.cs | 11 ++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs index 6c49379f52..1999c3caa0 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs @@ -1,6 +1,9 @@ #if UNITY_EDITOR using System; using System.Text; +#if ENABLE_NGO_ANALYTICS_LOGGING +using UnityEngine; +#endif using UnityEngine.Analytics; namespace Unity.Netcode.Editor @@ -32,7 +35,7 @@ public override string ToString() #if ENABLE_NGO_ANALYTICS_LOGGING internal void LogAnalytics(int sessionNumber) { - Debug.Log($"{ToString()}"); + Debug.Log($"{nameof(NetworkManagerAnalytics)} Session-{sessionNumber}:\n {ToString()}"); } #endif public bool Equals(NetworkManagerAnalytics other) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 01bedf3486..bbf38bccbb 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -3,6 +3,9 @@ using Unity.Netcode.Editor.Configuration; using UnityEditor; using UnityEngine; +#if ENABLE_NGO_ANALYTICS_LOGGING +using UnityEngine.Analytics; +#endif using UnityEngine.SceneManagement; namespace Unity.Netcode.Editor @@ -242,14 +245,16 @@ public void UpdateAnalytics() { var networkManagerAnalytics = GetNetworkManagerAnalytics(NetworkManager.RecentSessions[i]); -#if ENABLE_NGO_ANALYTICS_LOGGING - networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); -#endif + // If the previous session has no changes to the configuration then skip it (only unique configurations) if (previousAnalytics.Equals(networkManagerAnalytics)) { continue; } + +#if ENABLE_NGO_ANALYTICS_LOGGING + networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); +#endif var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); #if ENABLE_NGO_ANALYTICS_LOGGING if (result != AnalyticsResult.Ok) From 97fc86d14c603d09619a4dabd7c9807bd848c7cd Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 11 Mar 2025 15:38:30 -0500 Subject: [PATCH 08/11] update Refactored this a bit so we don't have a lot of the Analytics stuff within the NetworkManager, making some adjustments for integration testing and future analytics needs, and making sure we are not adding to our public API footprint. --- .../Editor/Analytics/AnalyticsHandler.cs | 2 + .../Editor/Analytics/NetcodeAnalytics.cs | 214 +++++++++ .../Editor/Analytics/NetcodeAnalytics.cs.meta | 2 + .../Analytics/NetworkManagerAnalytics.cs | 7 +- .../Editor/AssemblyInfo.cs | 1 + .../Editor/NetworkManagerHelper.cs | 84 +--- .../Runtime/Core/NetworkManager.cs | 406 +++++++++--------- .../ProjectSettings/ProjectSettings.asset | 4 +- 8 files changed, 446 insertions(+), 274 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs index e16dc23218..b366e225eb 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs @@ -8,6 +8,8 @@ internal class AnalyticsHandler : IAnalytic where T : IAnalytic.IData { private T m_Data; + internal T Data => m_Data; + public AnalyticsHandler(T data) { m_Data = data; diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs new file mode 100644 index 0000000000..8f75b935b2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs @@ -0,0 +1,214 @@ +#if UNITY_EDITOR +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UnityEngine.Analytics; + +namespace Unity.Netcode.Editor +{ + /// + /// Netcode for GameObjects Analytics Class + /// + internal class NetcodeAnalytics : NetworkManager.NetcodeAnalytics + { + /// + /// Determines if we are running an integration test of the analytics integration + /// + internal static bool IsIntegrationTest = false; +#if ENABLE_NGO_ANALYTICS_LOGGING + internal static bool EnableLogging = true; +#else + internal static bool EnableLogging = false; +#endif + + // Preserves the analytics enabled flag + private bool m_OriginalAnalyticsEnabled; + + internal override void OnOneTimeSetup() + { + m_OriginalAnalyticsEnabled = EditorAnalytics.enabled; + // By default, we always disable analytics during integration testing + EditorAnalytics.enabled = false; + } + + internal override void OnOneTimeTearDown() + { + // Reset analytics to the original value + EditorAnalytics.enabled = m_OriginalAnalyticsEnabled; + } + + internal List AnalyticsTestResults = new List(); + + internal List RecentSessions = new List(); + /// + /// Invoked from . + /// + /// The new state. + /// The current instance when play mode was entered. + internal override void ModeChanged(PlayModeStateChange playModeState, NetworkManager networkManager) + { + switch (playModeState) + { + case PlayModeStateChange.EnteredPlayMode: + { + if (IsIntegrationTest) + { + AnalyticsTestResults.Clear(); + } + break; + } + case PlayModeStateChange.ExitingPlayMode: + { + // Update analytics + UpdateAnalytics(networkManager); + break; + } + } + } + + /// + /// Editor Only + /// Invoked when the session is started. + /// + /// The instance when the session is started. + internal override void SessionStarted(NetworkManager networkManager) + { + // If analytics is disabled, then exit early + if (!EditorAnalytics.enabled) + { + return; + } + + var newSession = new NetworkSessionInfo() + { + SessionIndex = RecentSessions.Count, + WasClient = networkManager.IsClient, + WasServer = networkManager.IsServer, + NetworkConfig = networkManager.NetworkConfig.Copy(), + Transport = networkManager.NetworkConfig.NetworkTransport != null ? networkManager.NetworkConfig.NetworkTransport.GetType().Name : "None", + }; + RecentSessions.Add(newSession); + } + + /// + /// Editor Only + /// Invoked when the session is stopped or upon exiting play mode. + /// + /// The instance. + internal override void SessionStopped(NetworkManager networkManager) + { + // If analytics is disabled or there are no sessions, then exit early + if (!EditorAnalytics.enabled || RecentSessions.Count == 0) + { + return; + } + + var lastIndex = RecentSessions.Count - 1; + var recentSession = RecentSessions[lastIndex]; + // If the session has already been finalized, then exit early. + if (recentSession.SessionStopped) + { + return; + } + recentSession.UsedCMBService = networkManager.CMBServiceConnection; + recentSession.SessionStopped = true; + RecentSessions[lastIndex] = recentSession; + } + + /// + /// Invoked from within when exiting play mode. + /// + private void UpdateAnalytics(NetworkManager networkManager) + { + // Exit early if analytics is disabled or there are no sessions to process. + if (!EditorAnalytics.enabled || RecentSessions.Count == 0) + { + return; + } + + // If the NetworkManager isn't null, then make sure the last entry is marked off as stopped. + // If the last session is stopped, then SessionStopped will exit early. + if (networkManager != null) + { + SessionStopped(networkManager); + } + + // Parse through all of the recent network sessions to generate and send NetworkManager analytics + for (int i = 0; i < RecentSessions.Count; i++) + { + var networkManagerAnalytics = GetNetworkManagerAnalytics(RecentSessions[i]); + + var isDuplicate = false; + foreach (var analytics in AnalyticsTestResults) + { + // If we have any sessions with identical configurations, + // then we want to ignore those. + if (analytics.Data.Equals(networkManagerAnalytics)) + { + isDuplicate = true; + break; + } + } + + if (isDuplicate) + { + continue; + } + + if (!IsIntegrationTest) + { + var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); + if (EnableLogging && result != AnalyticsResult.Ok) + { + Debug.LogWarning($"[Analytics] Problem sending analytics: {result}"); + } + } + else + { + AnalyticsTestResults.Add(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); + } + } + + if (IsIntegrationTest && EnableLogging) + { + var count = 0; + foreach (var entry in AnalyticsTestResults) + { + entry.Data.LogAnalyticData(count); + count++; + } + } + RecentSessions.Clear(); + } + + /// + /// Generates a based on the passed in + /// + /// Represents a network session with the used NetworkManager configuration + /// + private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkSessionInfo networkSession) + { + var multiplayerSDKInstalled = false; +#if MULTIPLAYER_SERVICES_SDK_INSTALLED + multiplayerSDKInstalled = true; +#endif + if (EnableLogging && !networkSession.SessionStopped) + { + Debug.LogWarning($"Session-{networkSession.SessionIndex} was not considered stopped!"); + } + var networkManagerAnalytics = new NetworkManagerAnalytics() + { + IsDistributedAuthority = networkSession.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, + WasServer = networkSession.WasServer, + WasClient = networkSession.WasClient, + UsedCMBService = networkSession.UsedCMBService, + IsUsingMultiplayerSDK = multiplayerSDKInstalled, + NetworkTransport = networkSession.Transport, + EnableSceneManagement = networkSession.NetworkConfig.EnableSceneManagement, + TickRate = (int)networkSession.NetworkConfig.TickRate, + }; + return networkManagerAnalytics; + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta new file mode 100644 index 0000000000..757665a130 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 05223af7b06843841868225a65c90fea \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs index 1999c3caa0..9ba7e46142 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs @@ -1,9 +1,7 @@ #if UNITY_EDITOR using System; using System.Text; -#if ENABLE_NGO_ANALYTICS_LOGGING using UnityEngine; -#endif using UnityEngine.Analytics; namespace Unity.Netcode.Editor @@ -32,12 +30,11 @@ public override string ToString() message.AppendLine($"{nameof(TickRate)}: {TickRate}"); return message.ToString(); } -#if ENABLE_NGO_ANALYTICS_LOGGING - internal void LogAnalytics(int sessionNumber) + + internal void LogAnalyticData(int sessionNumber) { Debug.Log($"{nameof(NetworkManagerAnalytics)} Session-{sessionNumber}:\n {ToString()}"); } -#endif public bool Equals(NetworkManagerAnalytics other) { return IsDistributedAuthority == other.IsDistributedAuthority && WasServer == other.WasServer && WasClient == other.WasClient diff --git a/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs b/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs index f3fd38bc17..41e6599661 100644 --- a/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs +++ b/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs @@ -3,5 +3,6 @@ #if UNITY_INCLUDE_TESTS #if UNITY_EDITOR [assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")] +[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] #endif // UNITY_EDITOR #endif // UNITY_INCLUDE_TESTS diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index bbf38bccbb..98e132f0da 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -3,14 +3,22 @@ using Unity.Netcode.Editor.Configuration; using UnityEditor; using UnityEngine; -#if ENABLE_NGO_ANALYTICS_LOGGING -using UnityEngine.Analytics; -#endif using UnityEngine.SceneManagement; namespace Unity.Netcode.Editor { #if UNITY_EDITOR + internal struct NetworkSessionInfo + { + public int SessionIndex; + public bool SessionStopped; + public bool WasServer; + public bool WasClient; + public bool UsedCMBService; + public string Transport; + public NetworkConfig NetworkConfig; + } + /// /// Specialized editor specific NetworkManager code /// @@ -30,6 +38,7 @@ public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper private static void InitializeOnload() { Singleton = new NetworkManagerHelper(); + NetworkManager.NetworkManagerHelper = Singleton; EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged; EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged; @@ -228,74 +237,15 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool return isParented; } - /// - /// Invoked from within when exiting play mode. - /// - public void UpdateAnalytics() - { - // Exit early if analytics is disabled - if (!EditorAnalytics.enabled || NetworkManager.RecentSessions.Count == 0) - { - return; - } - - var previousAnalytics = new NetworkManagerAnalytics(); - // Parse through all of the recent network sessions to generate and send NetworkManager analytics - for (int i = 0; i < NetworkManager.RecentSessions.Count; i++) - { - var networkManagerAnalytics = GetNetworkManagerAnalytics(NetworkManager.RecentSessions[i]); - - - // If the previous session has no changes to the configuration then skip it (only unique configurations) - if (previousAnalytics.Equals(networkManagerAnalytics)) - { - continue; - } - -#if ENABLE_NGO_ANALYTICS_LOGGING - networkManagerAnalytics.LogAnalytics(NetworkManager.RecentSessions[i].SessionIndex); -#endif - var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); -#if ENABLE_NGO_ANALYTICS_LOGGING - if (result != AnalyticsResult.Ok) - { - Debug.LogWarning($"[Analytics] Problem sending analytics: {result}"); - } -#endif - previousAnalytics = networkManagerAnalytics; - } - } + internal NetcodeAnalytics NetcodeAnalytics = new NetcodeAnalytics(); /// - /// Generates a based on the passed in + /// Directly define the interface method to keep this internal /// - /// Represents a network session with the used NetworkManager configuration - /// - private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkManager.NetworkSessionInfo networkSession) + /// The instance which is derived from the abstract class. + NetworkManager.NetcodeAnalytics NetworkManager.INetworkManagerHelper.Analytics() { - var multiplayerSDKInstalled = false; -#if MULTIPLAYER_SERVICES_SDK_INSTALLED - multiplayerSDKInstalled = true; -#endif -#if ENABLE_NGO_ANALYTICS_LOGGING - if (!networkSession.SessionStopped) - { - Debug.LogWarning($"Session-{networkSession.SessionIndex} was not considered stopped!"); - } -#endif - - var networkManagerAnalytics = new NetworkManagerAnalytics() - { - IsDistributedAuthority = networkSession.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, - WasServer = networkSession.WasServer, - WasClient = networkSession.WasClient, - UsedCMBService = networkSession.UsedCMBService, - IsUsingMultiplayerSDK = multiplayerSDKInstalled, - NetworkTransport = networkSession.Transport, - EnableSceneManagement = networkSession.NetworkConfig.EnableSceneManagement, - TickRate = (int)networkSession.NetworkConfig.TickRate, - }; - return networkManagerAnalytics; + return NetcodeAnalytics; } } #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index cd1b133874..d564f0f8f3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -910,206 +910,6 @@ internal T Value internal Override PortOverride; - -#if UNITY_EDITOR - internal static INetworkManagerHelper NetworkManagerHelper; - - internal struct NetworkSessionInfo - { - public int SessionIndex; - public bool SessionStopped; - public bool WasServer; - public bool WasClient; - public bool UsedCMBService; - public string Transport; - public NetworkConfig NetworkConfig; - } - - internal static List RecentSessions = new List(); - - /// - /// Interface for NetworkManagerHelper - /// - internal interface INetworkManagerHelper - { - bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false); - void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false); - - void UpdateAnalytics(); - } - - internal delegate void ResetNetworkManagerDelegate(NetworkManager manager); - - internal static ResetNetworkManagerDelegate OnNetworkManagerReset; - - private void Reset() - { - OnNetworkManagerReset?.Invoke(this); - } - - protected virtual void OnValidateComponent() - { - - } - - private PackageInfo GetPackageInfo(string packageName) - { - return AssetDatabase.FindAssets("package").Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath(x) != null).Select(PackageInfo.FindForAssetPath).Where(x => x != null).First(x => x.name == packageName); - } - - internal void OnValidate() - { - if (NetworkConfig == null) - { - return; // May occur when the component is added - } - - // Do a validation pass on NetworkConfig properties - NetworkConfig.OnValidate(); - - if (GetComponentInChildren() != null) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}."); - } - } - - var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); - - // If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information - if (!activeScene.isDirty || EditorApplication.isUpdating) - { - return; - } - - // During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it - NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear(); - - var prefabs = NetworkConfig.Prefabs.Prefabs; - // Check network prefabs and assign to dictionary for quick look up - for (int i = 0; i < prefabs.Count; i++) - { - var networkPrefab = prefabs[i]; - var networkPrefabGo = networkPrefab?.Prefab; - if (networkPrefabGo == null) - { - continue; - } - - var networkObject = networkPrefabGo.GetComponent(); - if (networkObject == null) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogError($"Cannot register {NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root"); - } - - continue; - } - - { - var childNetworkObjects = new List(); - networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects); - if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)"); - } - } - } - } - - try - { - OnValidateComponent(); - } - catch (Exception ex) - { - Debug.LogException(ex); - } - } - - private void ModeChanged(PlayModeStateChange change) - { - if (change == PlayModeStateChange.EnteredPlayMode) - { - RecentSessions.Clear(); - } - - if (change == PlayModeStateChange.ExitingPlayMode) - { - try - { - EndNetworkSession(); - NetworkManagerHelper?.UpdateAnalytics(); - } - catch (Exception ex) - { - Debug.LogException(ex); - } - RecentSessions.Clear(); - - if (IsListening) - { - OnApplicationQuit(); - } - } - } - - /// - /// If analytics is enabled, this will create a new struct. - /// - /// - /// Invoked when NetworkManager is started. - /// - private void BeginNetworkSession() - { - if (!EditorAnalytics.enabled) - { - return; - } - var newSession = new NetworkSessionInfo() - { - SessionIndex = RecentSessions.Count, - WasClient = IsClient, - WasServer = IsServer, - NetworkConfig = NetworkConfig.Copy(), - Transport = NetworkConfig.NetworkTransport != null ? NetworkConfig.NetworkTransport.GetType().Name : "None", - }; - RecentSessions.Add(newSession); - } - - /// - /// If analytics is enabled, this will finalize the current struct. - /// - /// - /// Invoked when NetworkManager is stopped or upon exiting play mode. - /// - private void EndNetworkSession() - { - // If analytics is disabled, then exit early - if (!EditorAnalytics.enabled) - { - return; - } - if (RecentSessions.Count > 0) - { - var lastIndex = RecentSessions.Count - 1; - var recentSession = RecentSessions[lastIndex]; - // If the session has already been finalized, then exit early. - if (recentSession.SessionStopped) - { - return; - } - recentSession.UsedCMBService = CMBServiceConnection; - recentSession.SessionStopped = true; - RecentSessions[lastIndex] = recentSession; - } - } -#endif - /// /// Determines if the NetworkManager's GameObject is parented under another GameObject and /// notifies the user that this is not allowed for the NetworkManager. @@ -1813,5 +1613,211 @@ private void ParseCommandLineOptions() ParseArg(k_OverridePortArg, ref PortOverride); #endif } + +#if UNITY_EDITOR + internal static INetworkManagerHelper NetworkManagerHelper; + + /// + /// Interface for NetworkManagerHelper + /// + internal interface INetworkManagerHelper + { + bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false); + + void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false); + + internal NetcodeAnalytics Analytics(); + } + + internal abstract class NetcodeAnalytics + { + internal abstract void ModeChanged(PlayModeStateChange playModeState, NetworkManager networkManager); + + internal abstract void SessionStarted(NetworkManager networkManager); + + internal abstract void SessionStopped(NetworkManager networkManager); + + internal abstract void OnOneTimeSetup(); + + internal abstract void OnOneTimeTearDown(); + } + + internal delegate void ResetNetworkManagerDelegate(NetworkManager manager); + + internal static ResetNetworkManagerDelegate OnNetworkManagerReset; + + private void Reset() + { + OnNetworkManagerReset?.Invoke(this); + } + + /// + /// Invoked when validating the component. + /// + protected virtual void OnValidateComponent() + { + + } + + private PackageInfo GetPackageInfo(string packageName) + { + return AssetDatabase.FindAssets("package").Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath(x) != null).Select(PackageInfo.FindForAssetPath).Where(x => x != null).First(x => x.name == packageName); + } + + internal void OnValidate() + { + if (NetworkConfig == null) + { + return; // May occur when the component is added + } + + // Do a validation pass on NetworkConfig properties + NetworkConfig.OnValidate(); + + if (GetComponentInChildren() != null) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}."); + } + } + + var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); + + // If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information + if (!activeScene.isDirty || EditorApplication.isUpdating) + { + return; + } + + // During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it + NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear(); + + var prefabs = NetworkConfig.Prefabs.Prefabs; + // Check network prefabs and assign to dictionary for quick look up + for (int i = 0; i < prefabs.Count; i++) + { + var networkPrefab = prefabs[i]; + var networkPrefabGo = networkPrefab?.Prefab; + if (networkPrefabGo == null) + { + continue; + } + + var networkObject = networkPrefabGo.GetComponent(); + if (networkObject == null) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogError($"Cannot register {NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root"); + } + + continue; + } + + { + var childNetworkObjects = new List(); + networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects); + if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)"); + } + } + } + } + + try + { + OnValidateComponent(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + + internal void ModeChanged(PlayModeStateChange playModeState) + { + if (playModeState == PlayModeStateChange.ExitingPlayMode) + { + if (IsListening) + { + OnApplicationQuit(); + } + } + try + { + NetworkManagerHelper?.Analytics()?.ModeChanged(playModeState, this); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + + } + + /// + /// Invoked when NetworkManager is started. + /// + private void BeginNetworkSession() + { + try + { + NetworkManagerHelper?.Analytics()?.SessionStarted(this); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + + /// + /// Invoked when NetworkManager is stopped or upon exiting play mode. + /// + private void EndNetworkSession() + { + try + { + NetworkManagerHelper?.Analytics()?.SessionStopped(this); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } +#endif + +#if UNITY_INCLUDE_TESTS + /// + /// Used for integration tests + /// + internal static void OnOneTimeSetup() + { +#if UNITY_EDITOR + try + { + NetworkManagerHelper?.Analytics()?.OnOneTimeSetup(); + } + catch { } +#endif + } + + /// + /// Used for integration tests + /// + internal static void OnOneTimeTearDown() + { +#if UNITY_EDITOR + try + { + NetworkManagerHelper?.Analytics()?.OnOneTimeTearDown(); + } + catch { } +#endif + } +#endif + } } diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 9975ea1a15..492e94571c 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -17,8 +17,8 @@ PlayerSettings: defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} - m_ShowUnitySplashScreen: 1 - m_ShowUnitySplashLogo: 1 + m_ShowUnitySplashScreen: 0 + m_ShowUnitySplashLogo: 0 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 m_SplashScreenLogoStyle: 1 From 64467547f99f9fbc3339796d3c5cb1fe6039fbb1 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 11 Mar 2025 15:41:36 -0500 Subject: [PATCH 09/11] test Adding the integration test to validate the analytics data collection flow/process. Adding the ability to make adjustments during integration testing from assemblies outside of the TestHelper runtime scope. --- .../TestHelpers/Runtime/AssemblyInfo.cs | 1 + .../Runtime/NetcodeIntegrationTest.cs | 10 + .../Assets/Tests/Runtime/AnalyticsTests.cs | 202 ++++++++++++++++++ .../Tests/Runtime/AnalyticsTests.cs.meta | 2 + .../Runtime/testproject.runtimetests.asmdef | 1 + 5 files changed, 216 insertions(+) create mode 100644 testproject/Assets/Tests/Runtime/AnalyticsTests.cs create mode 100644 testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs index b21e378297..e880ab216e 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs @@ -4,6 +4,7 @@ [assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] [assembly: InternalsVisibleTo("TestProject.RuntimeTests")] #if UNITY_EDITOR +[assembly: InternalsVisibleTo("Unity.Netcode.Editor")] [assembly: InternalsVisibleTo("TestProject.EditorTests")] #endif // UNITY_EDITOR #if MULTIPLAYER_TOOLS diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index fc68b71b8b..95bf441e4d 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -301,6 +301,12 @@ public void OneTimeSetup() // Enable NetcodeIntegrationTest auto-label feature NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(true); + +#if UNITY_INCLUDE_TESTS + // Provide an external hook to be able to make adjustments to netcode classes prior to running any tests + NetworkManager.OnOneTimeSetup(); +#endif + OnOneTimeSetup(); VerboseDebug($"Exiting {nameof(OneTimeSetup)}"); @@ -1293,6 +1299,10 @@ public void OneTimeTearDown() UnloadRemainingScenes(); VerboseDebug($"Exiting {nameof(OneTimeTearDown)}"); +#if UNITY_INCLUDE_TESTS + // Provide an external hook to be able to make adjustments to netcode classes after running tests + NetworkManager.OnOneTimeTearDown(); +#endif IsRunning = false; } diff --git a/testproject/Assets/Tests/Runtime/AnalyticsTests.cs b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs new file mode 100644 index 0000000000..3db6bc103f --- /dev/null +++ b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs @@ -0,0 +1,202 @@ +#if UNITY_EDITOR +using System.Collections; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.Editor; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + /// + /// In-Editor only test
+ /// Validates the analytics event data collection process.
+ /// + ///
+ internal class AnalyticsTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + private NetcodeAnalytics m_NetcodeAnalytics; + + protected override IEnumerator OnSetup() + { + NetcodeAnalytics.IsIntegrationTest = true; + m_NetcodeAnalytics = Unity.Netcode.Editor.NetworkManagerHelper.Singleton.NetcodeAnalytics; + yield return base.OnSetup(); + } + + protected override IEnumerator OnTearDown() + { + NetcodeAnalytics.IsIntegrationTest = false; + yield return base.OnTearDown(); + } + + private bool m_NetworkManagerStarted; + private bool m_NetworkManagerStopped; + private IEnumerator StartAndStopSession(int expectedCount, bool isHost, bool isDistributedAuthority) + { + m_NetworkManagerStarted = false; + m_NetworkManagerStopped = false; + + m_ServerNetworkManager.NetworkConfig.NetworkTopology = isDistributedAuthority ? NetworkTopologyTypes.DistributedAuthority : NetworkTopologyTypes.ClientServer; + + if (!isHost) + { + m_ServerNetworkManager.StartServer(); + } + else + { + m_ServerNetworkManager.StartHost(); + } + yield return WaitForConditionOrTimeOut(() => m_NetworkManagerStarted); + var serverOrHost = isHost ? "Host" : "Server"; + AssertOnTimeout($"Failed to start {nameof(NetworkManager)} as a {serverOrHost} using a {m_ServerNetworkManager.NetworkConfig.NetworkTopology} {nameof(NetworkConfig.NetworkTopology)}!"); + + yield return StopNetworkManager(); + } + + private IEnumerator StopNetworkManager() + { + var serverOrHost = m_ServerNetworkManager.IsHost ? "Host" : "Server"; + + m_ServerNetworkManager.Shutdown(); + yield return WaitForConditionOrTimeOut(() => m_NetworkManagerStopped); + AssertOnTimeout($"Failed to stop {nameof(NetworkManager)} as a {serverOrHost} using a {m_ServerNetworkManager.NetworkConfig.NetworkTopology} {nameof(NetworkConfig.NetworkTopology)}!"); + } + + private void OnServerStarted() + { + m_NetworkManagerStarted = true; + } + + private void OnServerStopped(bool wasHost) + { + m_NetworkManagerStopped = true; + } + + /// + /// Validates the NGO analytics gathering process:
+ /// - When entering play mode any previous analytics events should be cleared.
+ /// - Each session while in play mode should be tracked (minimal processing).
+ /// - When exiting play mode the sessions should be processed and cleared.
+ /// - There should only be one unique analytic data event per unique session configuration.
+ ///
+ [UnityTest] + public IEnumerator ValidateCollectionProcess() + { + var expectedCount = 0; + var currentCount = 0; + m_ServerNetworkManager.OnServerStarted += OnServerStarted; + m_ServerNetworkManager.OnServerStopped += OnServerStopped; + m_PlayerPrefab.SetActive(false); + m_ServerNetworkManager.NetworkConfig.PlayerPrefab = null; + yield return StopNetworkManager(); + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == 1, $"Expected 1 session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == 0, $"Expected 0 analytics events but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + + // Simulate exiting play mode + m_NetcodeAnalytics.ModeChanged(UnityEditor.PlayModeStateChange.ExitingPlayMode, m_ServerNetworkManager); + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == 1, $"Expected 1 analytics event but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + + // Simulate entering play mode + m_NetcodeAnalytics.ModeChanged(UnityEditor.PlayModeStateChange.EnteredPlayMode, m_ServerNetworkManager); + + Assert.IsFalse(m_ServerNetworkManager.IsListening, $"Networkmanager should be stopped but is still listening!"); + // Client-Server + // Start as a Server and then shutdown + yield return StartAndStopSession(expectedCount, false, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a server again with the same settings and then shutdown + // (this should be excluded in the final analytics event data) + yield return StartAndStopSession(expectedCount, false, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a Host and then shutdown + yield return StartAndStopSession(expectedCount, true, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a DAHost and then shutdown + yield return StartAndStopSession(expectedCount, true, true); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a Host again and then shutdown + // (this should be excluded in the final analytics event data) + yield return StartAndStopSession(expectedCount, true, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == 0, $"Expected 0 analytics events but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + + ////////////////////////////////////////////////// + // Validating that each session's configuration is tracked for generating the analytics data events. + // Verify session 1 + var session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && !session.WasClient, $"Expected session to be started as a server session but it was not! Server: {session.WasServer} Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 2 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && !session.WasClient, $"Expected session to be started as a server session but it was not! Server: {session.WasServer} Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 3 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && session.WasClient, $"Expected session to be started as a host session but it was not! Server: {session.WasServer}Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 4 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && session.WasClient, $"Expected session to be started as a host session but it was not! Server: {session.WasServer}Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, $"Expected session to be a {NetworkTopologyTypes.DistributedAuthority} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 5 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer == true && session.WasClient, $"Expected session to be started as a host session but it was not! Server: {session.WasServer}Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + ////////////////////////////////////////////////// + // Validating that the analytics event data that would be sent should only contain information on the 3 unique session configurations + // Client-Server as a Server + // Client-Server as a Host + // Distributed Authority as a DAHost + m_NetcodeAnalytics.ModeChanged(UnityEditor.PlayModeStateChange.ExitingPlayMode, m_ServerNetworkManager); + + // Verify that we cleared out the NetcodeAnalytics.RecentSessions when we exited play mode + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == 0, $"After exiting play mode we expected 0 RecentSessions but had {m_NetcodeAnalytics.RecentSessions.Count}"); + + // Adjust our expected count to be 2 less than the RecentSessions since we intentionally included two identical sessions. + // (There should only be unique session configurations included in the final analytics event data entries) + expectedCount -= 2; + + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == expectedCount, $"Expected {expectedCount} analytics event but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + currentCount = 0; + // Verify event data 1 + var eventData = m_NetcodeAnalytics.AnalyticsTestResults[currentCount].Data; + Assert.True(eventData.WasServer && !eventData.WasClient, $"Expected session to be started as a server session but it was not! WasServer: {eventData.WasServer} WasClient: {eventData.WasClient}"); + Assert.True(!eventData.IsDistributedAuthority, $"Expected IsDistributedAuthority to be false but it was true!"); + + // Verify event data 2 + currentCount++; + eventData = m_NetcodeAnalytics.AnalyticsTestResults[currentCount].Data; + Assert.True(eventData.WasServer && eventData.WasClient, $"Expected session to be started as a Host session but it was not! WasServer: {eventData.WasServer} WasClient: {eventData.WasClient}"); + Assert.True(!eventData.IsDistributedAuthority, $"Expected IsDistributedAuthority to be false but it was true!"); + + // Verify event data 3 + currentCount++; + eventData = m_NetcodeAnalytics.AnalyticsTestResults[currentCount].Data; + Assert.True(eventData.WasServer && eventData.WasClient, $"Expected session to be started as a host session but it was not! WasServer: {eventData.WasServer} WasClient: {eventData.WasClient}"); + Assert.True(eventData.IsDistributedAuthority, $"Expected IsDistributedAuthority to be true but it was false!"); + yield return null; + } + } +} +#endif diff --git a/testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta new file mode 100644 index 0000000000..0075e04b38 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 095c486606a56464ab25cccb457df562 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef index 846250639c..80fb79d2d3 100644 --- a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef +++ b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef @@ -4,6 +4,7 @@ "references": [ "Unity.Netcode.Runtime", "Unity.Netcode.RuntimeTests", + "Unity.Netcode.Editor", "TestProject.ManualTests", "TestProject", "Unity.Addressables", From ac9cb1ab69a159d62ba8b9a0a9f4dcc7c0156c0b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 11 Mar 2025 15:50:04 -0500 Subject: [PATCH 10/11] update moving the NetworkSessionInfo struct into the NetcodeAnalytics file. --- .../Editor/Analytics/NetcodeAnalytics.cs | 14 ++++++++++++++ .../Editor/NetworkManagerHelper.cs | 11 ----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs index 8f75b935b2..00b36d6111 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs @@ -6,6 +6,20 @@ namespace Unity.Netcode.Editor { + /// + /// Used to collection network session configuration information + /// + internal struct NetworkSessionInfo + { + public int SessionIndex; + public bool SessionStopped; + public bool WasServer; + public bool WasClient; + public bool UsedCMBService; + public string Transport; + public NetworkConfig NetworkConfig; + } + /// /// Netcode for GameObjects Analytics Class /// diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 98e132f0da..5cba906f2a 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -8,17 +8,6 @@ namespace Unity.Netcode.Editor { #if UNITY_EDITOR - internal struct NetworkSessionInfo - { - public int SessionIndex; - public bool SessionStopped; - public bool WasServer; - public bool WasClient; - public bool UsedCMBService; - public string Transport; - public NetworkConfig NetworkConfig; - } - /// /// Specialized editor specific NetworkManager code /// From 635688facc974471ae28168fdd7597802083bffb Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 11 Mar 2025 17:53:54 -0500 Subject: [PATCH 11/11] test - fix I think integration tests failed on the first pass because EditorAnalytics.enabled is defaulted to false during CI. This update should resolve the issue if that is the case. --- .../Editor/Analytics/NetcodeAnalytics.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs index 00b36d6111..4f1c4f8084 100644 --- a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs @@ -87,8 +87,8 @@ internal override void ModeChanged(PlayModeStateChange playModeState, NetworkMan /// The instance when the session is started. internal override void SessionStarted(NetworkManager networkManager) { - // If analytics is disabled, then exit early - if (!EditorAnalytics.enabled) + // If analytics is disabled and we are not running an integration test, then exit early. + if (!EditorAnalytics.enabled && !IsIntegrationTest) { return; } @@ -111,8 +111,8 @@ internal override void SessionStarted(NetworkManager networkManager) /// The instance. internal override void SessionStopped(NetworkManager networkManager) { - // If analytics is disabled or there are no sessions, then exit early - if (!EditorAnalytics.enabled || RecentSessions.Count == 0) + // If analytics is disabled and we are not running an integration test or there are no sessions, then exit early. + if ((!EditorAnalytics.enabled && !IsIntegrationTest) || RecentSessions.Count == 0) { return; } @@ -134,8 +134,8 @@ internal override void SessionStopped(NetworkManager networkManager) ///
private void UpdateAnalytics(NetworkManager networkManager) { - // Exit early if analytics is disabled or there are no sessions to process. - if (!EditorAnalytics.enabled || RecentSessions.Count == 0) + // If analytics is disabled and we are not running an integration test or there are no sessions to process, then exit early. + if ((!EditorAnalytics.enabled && !IsIntegrationTest) || RecentSessions.Count == 0) { return; } @@ -169,6 +169,7 @@ private void UpdateAnalytics(NetworkManager networkManager) continue; } + // If not running an integration test, then go ahead and send the anlytics event data. if (!IsIntegrationTest) { var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics));