Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions com.unity.netcode.gameobjects/Editor/Analytics.meta

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if UNITY_EDITOR
using System;
using UnityEngine.Analytics;

namespace Unity.Netcode.Editor
{
internal class AnalyticsHandler<T> : IAnalytic where T : IAnalytic.IData
{
private T m_Data;

internal T Data => 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

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

229 changes: 229 additions & 0 deletions com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Analytics;

namespace Unity.Netcode.Editor
{
/// <summary>
/// Used to collection network session configuration information
/// </summary>
internal struct NetworkSessionInfo
{
public int SessionIndex;
public bool SessionStopped;
public bool WasServer;
public bool WasClient;
public bool UsedCMBService;
public string Transport;
public NetworkConfig NetworkConfig;
}

/// <summary>
/// Netcode for GameObjects Analytics Class
/// </summary>
internal class NetcodeAnalytics : NetworkManager.NetcodeAnalytics
{
/// <summary>
/// Determines if we are running an integration test of the analytics integration
/// </summary>
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<NetworkManagerAnalyticsHandler> AnalyticsTestResults = new List<NetworkManagerAnalyticsHandler>();

internal List<NetworkSessionInfo> RecentSessions = new List<NetworkSessionInfo>();
/// <summary>
/// Invoked from <see cref="NetworkManager.ModeChanged(PlayModeStateChange)"/>.
/// </summary>
/// <param name="playModeState">The new <see cref="PlayModeStateChange"/> state.</param>
/// <param name="networkManager">The current <see cref="NetworkManager"/> instance when play mode was entered.</param>
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;
}
}
}

/// <summary>
/// Editor Only
/// Invoked when the session is started.
/// </summary>
/// <param name="networkManager">The <see cref="NetworkManager"/> instance when the session is started.</param>
internal override void SessionStarted(NetworkManager networkManager)
{
// If analytics is disabled and we are not running an integration test, then exit early.
if (!EditorAnalytics.enabled && !IsIntegrationTest)
{
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);
}

/// <summary>
/// Editor Only
/// Invoked when the session is stopped or upon exiting play mode.
/// </summary>
/// <param name="networkManager">The <see cref="NetworkManager"/> instance.</param>
internal override void SessionStopped(NetworkManager networkManager)
{
// 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;
}

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;
}

/// <summary>
/// Invoked from within <see cref="NetworkManager.ModeChanged"/> when exiting play mode.
/// </summary>
private void UpdateAnalytics(NetworkManager networkManager)
{
// 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;
}

// 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 not running an integration test, then go ahead and send the anlytics event data.
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();
}

/// <summary>
/// Generates a <see cref="NetworkManagerAnalytics"/> based on the <see cref="NetworkManager.NetworkSessionInfo"/> passed in
/// </summary>
/// <param name="networkSession">Represents a network session with the used NetworkManager configuration</param>
/// <returns></returns>
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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#if UNITY_EDITOR
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Analytics;

namespace Unity.Netcode.Editor
{
[Serializable]
internal struct NetworkManagerAnalytics : IAnalytic.IData, IEquatable<NetworkManagerAnalytics>
{
public bool IsDistributedAuthority;
public bool WasServer;
public bool WasClient;
public bool UsedCMBService;
public bool IsUsingMultiplayerSDK;
public string NetworkTransport;
public bool EnableSceneManagement;
public int TickRate;
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(UsedCMBService)}: {UsedCMBService}");
message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}");
message.AppendLine($"{nameof(NetworkTransport)}: {NetworkTransport}");
message.AppendLine($"{nameof(EnableSceneManagement)}: {EnableSceneManagement}");
message.AppendLine($"{nameof(TickRate)}: {TickRate}");
return message.ToString();
}

internal void LogAnalyticData(int sessionNumber)
{
Debug.Log($"{nameof(NetworkManagerAnalytics)} Session-{sessionNumber}:\n {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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#if UNITY_EDITOR
using UnityEngine.Analytics;

namespace Unity.Netcode.Editor
{
[AnalyticInfo("NGO_NetworkManager", "unity.netcode", 5, 100, 1000)]
internal class NetworkManagerAnalyticsHandler : AnalyticsHandler<NetworkManagerAnalytics>
{
public NetworkManagerAnalyticsHandler(NetworkManagerAnalytics networkManagerAnalytics) : base(networkManagerAnalytics) { }
}
}
#endif

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

1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper
private static void InitializeOnload()
{
Singleton = new NetworkManagerHelper();

NetworkManager.NetworkManagerHelper = Singleton;
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
Expand Down Expand Up @@ -224,6 +225,17 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool
}
return isParented;
}

internal NetcodeAnalytics NetcodeAnalytics = new NetcodeAnalytics();

/// <summary>
/// Directly define the interface method to keep this internal
/// </summary>
/// <returns>The <see cref="NetcodeAnalytics"/> instance which is derived from the <see cref="NetworkManager.NetcodeAnalytics"/> abstract class.</returns>
NetworkManager.NetcodeAnalytics NetworkManager.INetworkManagerHelper.Analytics()
{
return NetcodeAnalytics;
}
}
#endif
}
Loading