diff --git a/com.unity.multiplayer.mlapi/Runtime/Core/NetworkManager.cs b/com.unity.multiplayer.mlapi/Runtime/Core/NetworkManager.cs
index 7b0ddf8239..6aa2affa9f 100644
--- a/com.unity.multiplayer.mlapi/Runtime/Core/NetworkManager.cs
+++ b/com.unity.multiplayer.mlapi/Runtime/Core/NetworkManager.cs
@@ -29,7 +29,7 @@ namespace MLAPI
/// The main component of the library
///
[AddComponentMenu("MLAPI/NetworkManager", -100)]
- public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
+ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem, IProfilableTransportProvider
{
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
@@ -56,10 +56,6 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
internal RpcQueueContainer RpcQueueContainer { get; private set; }
internal NetworkTickSystem NetworkTickSystem { get; private set; }
- public delegate void PerformanceDataEventHandler(PerformanceTickData profilerData);
-
- public static event PerformanceDataEventHandler OnPerformanceDataEvent;
-
///
/// A synchronized time, represents the time in seconds since the server application started. Is replicated across all clients
///
@@ -386,6 +382,8 @@ private void Init(bool server)
NetworkConfig.NetworkTransport.ResetChannelCache();
NetworkConfig.NetworkTransport.Init();
+
+ ProfilerNotifier.Initialize(this);
}
///
@@ -1483,34 +1481,17 @@ internal void HandleApproval(ulong clientId, bool createPlayerObject, ulong? pla
private void ProfilerBeginTick()
{
- PerformanceDataManager.BeginNewTick();
- if (NetworkConfig.NetworkTransport is ITransportProfilerData profileTransport)
- {
- profileTransport.BeginNewTick();
- }
+ ProfilerNotifier.ProfilerBeginTick();
}
private void NotifyProfilerListeners()
{
- var data = PerformanceDataManager.GetData();
- var eventHandler = OnPerformanceDataEvent;
- if (eventHandler != null)
- {
- if (data != null)
- {
- if (NetworkConfig.NetworkTransport is ITransportProfilerData profileTransport)
- {
- var transportProfilerData = profileTransport.GetTransportProfilerData();
- PerformanceDataManager.AddTransportData(transportProfilerData);
- }
+ ProfilerNotifier.NotifyProfilerListeners();
+ }
- eventHandler.Invoke(data);
- }
- else
- {
- NetworkLog.LogWarning($"No data available. Did you forget to call {nameof(PerformanceDataManager)}.{nameof(PerformanceDataManager.BeginNewTick)}() first?");
- }
- }
+ public ITransportProfilerData Transport
+ {
+ get { return NetworkConfig.NetworkTransport as ITransportProfilerData; }
}
}
}
diff --git a/com.unity.multiplayer.mlapi/Runtime/Profiling/IProfilableTransportProvider.cs b/com.unity.multiplayer.mlapi/Runtime/Profiling/IProfilableTransportProvider.cs
new file mode 100644
index 0000000000..1776368e1d
--- /dev/null
+++ b/com.unity.multiplayer.mlapi/Runtime/Profiling/IProfilableTransportProvider.cs
@@ -0,0 +1,7 @@
+namespace MLAPI.Profiling
+{
+ public interface IProfilableTransportProvider
+ {
+ ITransportProfilerData Transport { get; }
+ }
+}
diff --git a/com.unity.multiplayer.mlapi/Runtime/Profiling/IProfilableTransportProvider.cs.meta b/com.unity.multiplayer.mlapi/Runtime/Profiling/IProfilableTransportProvider.cs.meta
new file mode 100644
index 0000000000..dbcd7976d2
--- /dev/null
+++ b/com.unity.multiplayer.mlapi/Runtime/Profiling/IProfilableTransportProvider.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 02cc48a5664f497ca5c792b68cb15ba0
+timeCreated: 1616427501
\ No newline at end of file
diff --git a/com.unity.multiplayer.mlapi/Runtime/Profiling/PerformanceDataManager.cs b/com.unity.multiplayer.mlapi/Runtime/Profiling/PerformanceDataManager.cs
index 2c9442e895..c94c19c962 100644
--- a/com.unity.multiplayer.mlapi/Runtime/Profiling/PerformanceDataManager.cs
+++ b/com.unity.multiplayer.mlapi/Runtime/Profiling/PerformanceDataManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using UnityEngine;
namespace MLAPI.Profiling
{
@@ -11,8 +12,9 @@ internal static class PerformanceDataManager
internal static void BeginNewTick()
{
s_TickId = Math.Max(s_TickId, 0);
+ s_TickId++;
s_ProfilerData.Reset();
- s_ProfilerData.TickId = s_TickId++;
+ s_ProfilerData.TickId = s_TickId;
}
internal static void Increment(string fieldName, int count = 1)
diff --git a/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerCountersInfo.cs b/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerCountersInfo.cs
index 8a9fd576bf..e4977db766 100644
--- a/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerCountersInfo.cs
+++ b/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerCountersInfo.cs
@@ -83,7 +83,13 @@ internal static class ProfilerCountersInfo
private static void RegisterMLAPIPerformanceEvent()
{
InitializeCounters();
- NetworkManager.OnPerformanceDataEvent += OnPerformanceTickData;
+ ProfilerNotifier.OnPerformanceDataEvent += OnPerformanceTickData;
+ ProfilerNotifier.OnNoTickDataEvent += OnNoTickData;
+ }
+
+ private static void OnNoTickData()
+ {
+ Debug.LogWarning("There was a profiler event that was not captured in a tick");
}
private static void InitializeCounters()
diff --git a/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerNotifier.cs b/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerNotifier.cs
new file mode 100644
index 0000000000..4a20e0083b
--- /dev/null
+++ b/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerNotifier.cs
@@ -0,0 +1,76 @@
+using System;
+using MLAPI.Logging;
+
+namespace MLAPI.Profiling
+{
+ public static class ProfilerNotifier
+ {
+ public delegate void PerformanceDataEventHandler(PerformanceTickData profilerData);
+
+ public static event PerformanceDataEventHandler OnPerformanceDataEvent;
+
+ public delegate void NoTickDataHandler();
+
+ public static event NoTickDataHandler OnNoTickDataEvent;
+
+ private static IProfilableTransportProvider s_ProfilableTransportProvider;
+ private static bool s_FailsafeCheck;
+
+ public static void Initialize(IProfilableTransportProvider profilableNetwork)
+ {
+ s_ProfilableTransportProvider = profilableNetwork
+ ?? throw new ArgumentNullException(
+ $"{nameof(profilableNetwork)} was not set");
+ s_FailsafeCheck = false;
+ }
+
+ public static void ProfilerBeginTick()
+ {
+ PerformanceDataManager.BeginNewTick();
+ var transport = s_ProfilableTransportProvider.Transport;
+ transport?.BeginNewTick();
+ s_FailsafeCheck = true;
+ }
+
+ public static void NotifyProfilerListeners()
+ {
+ if (!s_FailsafeCheck)
+ return;
+
+ s_FailsafeCheck = false;
+
+ var data = PerformanceDataManager.GetData();
+ var eventHandler = OnPerformanceDataEvent;
+ if (eventHandler != null)
+ {
+ if (data != null)
+ {
+ var transport = s_ProfilableTransportProvider.Transport;
+ if (transport != null)
+ {
+ var transportProfilerData = transport.GetTransportProfilerData();
+
+ PerformanceDataManager.AddTransportData(transportProfilerData);
+ }
+
+ eventHandler.Invoke(data);
+ }
+ else
+ {
+ NetworkLog.LogWarning(
+ "No data available. Did you forget to call PerformanceDataManager.BeginNewTick() first?");
+ }
+ }
+ }
+
+ public static void Increment(string fieldName, int count = 1)
+ {
+ if (!s_FailsafeCheck)
+ {
+ OnNoTickDataEvent?.Invoke();
+ }
+
+ PerformanceDataManager.Increment(fieldName);
+ }
+ }
+}
diff --git a/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerNotifier.cs.meta b/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerNotifier.cs.meta
new file mode 100644
index 0000000000..015d08aa4b
--- /dev/null
+++ b/com.unity.multiplayer.mlapi/Runtime/Profiling/ProfilerNotifier.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 12af6f0ff6554caaab917445ae94d809
+timeCreated: 1616427513
\ No newline at end of file
diff --git a/com.unity.multiplayer.mlapi/Tests/Editor/ProfilerTests.cs b/com.unity.multiplayer.mlapi/Tests/Editor/ProfilerTests.cs
new file mode 100644
index 0000000000..77e0cee67e
--- /dev/null
+++ b/com.unity.multiplayer.mlapi/Tests/Editor/ProfilerTests.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using MLAPI.Profiling;
+using NUnit.Framework;
+using UnityEditor;
+
+namespace MLAPI.RuntimeTests
+{
+ public class TestTransport : ITransportProfilerData
+ {
+ internal static class ProfilerConstants
+ {
+ public const string TransportTestData = nameof(TransportTestData);
+ }
+
+ private static readonly ProfilingDataStore TransportProfilerData = new ProfilingDataStore();
+
+ public void BeginNewTick()
+ {
+ TransportProfilerData.Clear();
+ }
+
+ public IReadOnlyDictionary GetTransportProfilerData()
+ {
+ return TransportProfilerData.GetReadonly();
+ }
+
+ public void Send(string testMessage)
+ {
+ PerformanceDataManager.Increment(ProfilerConstants.TransportTestData);
+ }
+ }
+
+ public class TestProfiler : IProfilableTransportProvider
+ {
+ internal static class ProfilerConstants
+ {
+ public const string NetworkTestData = nameof(NetworkTestData);
+ }
+
+ private TestTransport m_Transport;
+ private bool m_HasSentAnyData;
+
+ public ITransportProfilerData Transport => m_Transport;
+ public bool HasSentAnyData => m_HasSentAnyData;
+
+ public void Initialize(bool useNullTransport)
+ {
+ m_Transport = useNullTransport ? null : new TestTransport();
+ m_HasSentAnyData = false;
+ ProfilerNotifier.Initialize(this);
+ }
+
+ public static void ProfilerBeginTick()
+ {
+ ProfilerNotifier.ProfilerBeginTick();
+ }
+
+ public static void NotifyProfilerListeners()
+ {
+ ProfilerNotifier.NotifyProfilerListeners();
+ }
+
+ public void Send()
+ {
+ ProfilerNotifier.Increment(ProfilerConstants.NetworkTestData);
+ m_Transport?.Send("testMessage");
+ m_HasSentAnyData = true;
+ }
+ }
+
+ public class NoTickDataException : Exception
+ {
+ }
+
+ public class ProfilerTests
+ {
+ private static TestProfiler SetupTestProfiler(bool useNullTransport)
+ {
+ var testProfiler = new TestProfiler();
+ testProfiler.Initialize(useNullTransport);
+ return testProfiler;
+ }
+
+ private static void RegisterStaticAsserts(bool useNullTransport)
+ {
+ ProfilerNotifier.OnNoTickDataEvent += RaiseExceptionNoTickDataEvent;
+ if (useNullTransport)
+ {
+ ProfilerNotifier.OnPerformanceDataEvent += AssertNetworkDataExists;
+ }
+ else
+ {
+ ProfilerNotifier.OnPerformanceDataEvent += AssertNetworkAndTransportDataExists;
+ }
+ }
+
+ private static void AssertNetworkAndTransportDataExists(PerformanceTickData profilerData)
+ {
+ Assert.IsTrue(profilerData.HasData(TestProfiler.ProfilerConstants.NetworkTestData));
+ Assert.IsTrue(profilerData.HasData(TestTransport.ProfilerConstants.TransportTestData));
+ }
+
+ private static void AssertNetworkDataExists(PerformanceTickData profilerData)
+ {
+ Assert.IsTrue(profilerData.HasData(TestProfiler.ProfilerConstants.NetworkTestData));
+ Assert.IsFalse(profilerData.HasData(TestTransport.ProfilerConstants.TransportTestData));
+ }
+
+ private static void RaiseExceptionNoTickDataEvent()
+ {
+ throw new NoTickDataException();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ ProfilerNotifier.OnPerformanceDataEvent -= AssertNetworkAndTransportDataExists;
+ ProfilerNotifier.OnPerformanceDataEvent -= AssertNetworkDataExists;
+ ProfilerNotifier.OnNoTickDataEvent -= RaiseExceptionNoTickDataEvent;
+ }
+
+ [Test]
+ public void TestSentNoData()
+ {
+ const bool useNullTransport = true;
+ TestProfiler testProfiler = SetupTestProfiler(useNullTransport);
+
+ TestProfiler.ProfilerBeginTick();
+ TestProfiler.NotifyProfilerListeners();
+
+ Assert.False(testProfiler.HasSentAnyData);
+ }
+
+ [Test]
+ public void TestNormalRegisterAndNotifyFlow_NullTransport()
+ {
+ const bool useNullTransport = true;
+ TestProfiler testProfiler = SetupTestProfiler(useNullTransport);
+
+ RegisterStaticAsserts(useNullTransport);
+
+ TestProfiler.ProfilerBeginTick();
+ testProfiler.Send();
+ TestProfiler.NotifyProfilerListeners();
+
+ Assert.IsTrue(testProfiler.HasSentAnyData);
+ }
+
+ [Test]
+ public void TestNormalRegisterAndNotifyFlow()
+ {
+ const bool useNullTransport = false;
+ TestProfiler testProfiler = SetupTestProfiler(useNullTransport);
+
+ RegisterStaticAsserts(useNullTransport);
+
+ TestProfiler.ProfilerBeginTick();
+ testProfiler.Send();
+ TestProfiler.NotifyProfilerListeners();
+
+ Assert.IsTrue(testProfiler.HasSentAnyData);
+ }
+
+ [Test]
+ public void TestDroppedRegisterAndNotifyFlow()
+ {
+ const bool useNullTransport = false;
+ TestProfiler testProfiler = SetupTestProfiler(useNullTransport);
+
+ RegisterStaticAsserts(useNullTransport);
+
+ TestProfiler.ProfilerBeginTick();
+ testProfiler.Send();
+ TestProfiler.NotifyProfilerListeners();
+
+ // Capturing data after notifying listeners is bad
+ Assert.Catch(() =>
+ {
+ testProfiler.Send();
+ });
+
+ Assert.IsTrue(testProfiler.HasSentAnyData);
+ }
+
+ [Test]
+ public void TestProperMatchRegisterAndNotifyFlow()
+ {
+ const bool useNullTransport = false;
+ TestProfiler testProfiler = SetupTestProfiler(useNullTransport);
+
+ RegisterStaticAsserts(useNullTransport);
+
+ TestProfiler.NotifyProfilerListeners();
+ TestProfiler.ProfilerBeginTick();
+ testProfiler.Send();
+ TestProfiler.NotifyProfilerListeners();
+
+ Assert.IsTrue(testProfiler.HasSentAnyData);
+ }
+ }
+}
diff --git a/com.unity.multiplayer.mlapi/Tests/Editor/ProfilerTests.cs.meta b/com.unity.multiplayer.mlapi/Tests/Editor/ProfilerTests.cs.meta
new file mode 100644
index 0000000000..ff71e12578
--- /dev/null
+++ b/com.unity.multiplayer.mlapi/Tests/Editor/ProfilerTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 69750fc1f921f490fabad933b42ff9c5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: