using System; using System.Collections.Generic; using System.Linq; // ReSharper disable DelegateSubtraction #if UNITY_EDITOR using UnityEditor; using UnityEditor.Networking.PlayerConnection; #endif using UnityEngine.Networking.PlayerConnection; using UnityEngine.ResourceManagement.Util; namespace UnityEngine.ResourceManagement.Diagnostics { /// /// Collects ResourceManager events and passed them on the registered event handlers. In editor play mode, events are passed directly to the ResourceManager profiler window. /// In player builds, events are sent to the editor via the EditorConnection API. /// public class DiagnosticEventCollectorSingleton : ComponentSingleton { static Guid s_editorConnectionGuid; internal Dictionary m_CreatedEvents = new Dictionary(); internal List m_UnhandledEvents = new List(); internal DelegateList s_EventHandlers = DelegateList.CreateWithGlobalCache(); /// /// The guid used for the PlayerConnect messaging system. /// public static Guid PlayerConnectionGuid { get { if (s_editorConnectionGuid == Guid.Empty) s_editorConnectionGuid = new Guid(1, 2, 3, new byte[] { 20, 1, 32, 32, 4, 9, 6, 44 }); return s_editorConnectionGuid; } } /// protected override string GetGameObjectName() => "EventCollector"; /// /// Register for diagnostic events. If there is no collector, this will fail and return false. /// /// The handler method action. /// Register or unregister. /// If true, the event collector will be created if needed. /// True if registered, false if not. public static bool RegisterEventHandler(Action handler, bool register, bool create) { if (register && (create || Exists)) { Instance.RegisterEventHandler(handler); return true; } if (!register && Exists) { Instance.UnregisterEventHandler(handler); } return false; } internal void RegisterEventHandler(Action handler) { Debug.Assert(m_UnhandledEvents != null, "DiagnosticEventCollectorSingleton.RegisterEventHandler - s_unhandledEvents == null."); if (handler == null) throw new ArgumentNullException("handler"); s_EventHandlers.Add(handler); //Ensure that events are handled in frame order var combinedAndSortedList = m_UnhandledEvents.Concat(m_CreatedEvents.Values).OrderBy(evt => evt.Frame); foreach (var evt in combinedAndSortedList) handler(evt); m_UnhandledEvents.Clear(); } /// /// Unregister event hander /// /// Method or delegate that will handle the events public void UnregisterEventHandler(Action handler) { if (handler == null) throw new ArgumentNullException("handler"); s_EventHandlers.Remove(handler); } /// /// Send a event to all registered handlers /// /// The event to send public void PostEvent(DiagnosticEvent diagnosticEvent) { if (diagnosticEvent.Stream == (int)ResourceManager.DiagnosticEventType.AsyncOperationCreate && !m_CreatedEvents.ContainsKey(diagnosticEvent.ObjectId)) m_CreatedEvents.Add(diagnosticEvent.ObjectId, diagnosticEvent); else if (diagnosticEvent.Stream == (int)ResourceManager.DiagnosticEventType.AsyncOperationDestroy) m_CreatedEvents.Remove(diagnosticEvent.ObjectId); Debug.Assert(m_UnhandledEvents != null, "DiagnosticEventCollectorSingleton.PostEvent - s_unhandledEvents == null."); if (s_EventHandlers.Count > 0) s_EventHandlers.Invoke(diagnosticEvent); else m_UnhandledEvents.Add(diagnosticEvent); } void Awake() { #if !UNITY_EDITOR RegisterEventHandler((DiagnosticEvent diagnosticEvent) => {PlayerConnection.instance.Send(DiagnosticEventCollectorSingleton.PlayerConnectionGuid, diagnosticEvent.Serialize()); }); #endif } float m_lastTickSent = 0; int m_lastFrame = 0; float fpsAvg = 30; void Update() { if (s_EventHandlers.Count > 0) { var elapsed = Time.realtimeSinceStartup - m_lastTickSent; if (elapsed > .25f) { var fps = (Time.frameCount - m_lastFrame) / elapsed; m_lastFrame = Time.frameCount; fpsAvg = (fpsAvg + fps) * .5f; m_lastTickSent = Time.realtimeSinceStartup; int heapKB = (int)(Profiling.Profiler.GetMonoUsedSizeLong() / 1024); PostEvent(new DiagnosticEvent("FrameCount", "FPS", 2, 1, Time.frameCount, (int)fpsAvg, null)); PostEvent(new DiagnosticEvent("MemoryCount", "MonoHeap", 3, 2, Time.frameCount, heapKB, null)); } } } } /// /// Collects ResourceManager events and passed them on the registered event handlers. In editor play mode, events are passed directly to the ResourceManager profiler window. /// In player builds, events are sent to the editor via the EditorConnection API. /// public class DiagnosticEventCollector : MonoBehaviour { static DiagnosticEventCollector s_Collector; /// /// The guid used for the PlayerConnect messaging system. /// public static Guid PlayerConnectionGuid => DiagnosticEventCollectorSingleton.PlayerConnectionGuid; /// /// Retrieves the global event collector. A new one is created if needed. /// /// The event collector global instance. public static DiagnosticEventCollector FindOrCreateGlobalInstance() { if (s_Collector == null) { var go = new GameObject("EventCollector", typeof(DiagnosticEventCollector)); s_Collector = go.GetComponent(); go.hideFlags = HideFlags.DontSave;// HideFlags.HideAndDontSave; } return s_Collector; } /// /// Register for diagnostic events. If there is no collector, this will fail and return false. /// /// The handler method action. /// Register or unregister. /// If true, the event collector will be created if needed. /// True if registered, false if not. public static bool RegisterEventHandler(Action handler, bool register, bool create) => DiagnosticEventCollectorSingleton.RegisterEventHandler(handler, register, create); /// /// Unregister event hander /// /// Method or delegate that will handle the events public void UnregisterEventHandler(Action handler) => DiagnosticEventCollectorSingleton.Instance.UnregisterEventHandler(handler); /// /// Send a event to all registered handlers /// /// The event to send public void PostEvent(DiagnosticEvent diagnosticEvent) => DiagnosticEventCollectorSingleton.Instance.PostEvent(diagnosticEvent); #if UNITY_EDITOR public static class PlayStateNotifier { } #endif } }