//#define ANALYTICS_DEBUG using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using UnityEngine.Analytics; using UnityEngine.Timeline; namespace UnityEditor.Timeline { class TimelineWindowAnalytics { const string vendorKey = "unity.timeline"; const string eventName = "timeline_editor_info"; const int version = 2; const int maxEventsPerHour = 1000; const int maxNumberOfElements = 1000; [Serializable] internal struct timeline_asset_stats #if UNITY_2023_2_OR_NEWER : IAnalytic.IData #endif { public string asset_guid; public double duration; public double frame_rate; public List track_stats; public double mix_samples_count, ripple_samples_count, replace_samples_count; public string display_format; } [Serializable] internal struct track_asset_stats { public string track_type; public int clip_count; public int marker_count; } class WindowAnalyticsStats { internal int[] editModeSamples = new int[3]; // EditModes } #if UNITY_2023_2_OR_NEWER [AnalyticInfo( eventName: eventName, vendorKey: vendorKey, version: version, maxEventsPerHour: maxEventsPerHour, maxNumberOfElements: maxNumberOfElements)] class TimelineWindowAnalyticsEvent : IAnalytic { public timeline_asset_stats timelineStats { get; } bool m_CanSendData; public TimelineWindowAnalyticsEvent() { m_CanSendData = GenerateTimelineAssetStats(out timeline_asset_stats data); timelineStats = data; } public void Send() { if (m_CanSendData) EditorAnalytics.SendAnalytic(this); } bool IAnalytic.TryGatherData(out IAnalytic.IData data, out Exception error) { error = null; data = timelineStats; return true; } } #endif static WindowAnalyticsStats analyticsStats = new WindowAnalyticsStats(); public void SendPlayEvent(bool start) { if (!start || !EditorAnalytics.enabled) return; #if UNITY_2023_2_OR_NEWER var analyticsEvent = new TimelineWindowAnalyticsEvent(); LogAnalyticsData(analyticsEvent.timelineStats); analyticsEvent.Send(); #else EditorAnalytics.RegisterEventWithLimit(eventName, maxEventsPerHour, maxNumberOfElements, vendorKey, version); var ret = GenerateTimelineAssetStats(out var data); if (!ret) return; LogAnalyticsData(data); EditorAnalytics.SendEventWithLimit(eventName, data, version); #endif SendAfterSequenceChangeEvent(); } public void SendAfterSequenceChangeEvent() { analyticsStats = new WindowAnalyticsStats(); // Wipe Window Stats } public void SendManipulationEndedEvent() { analyticsStats.editModeSamples[(int)EditMode.editType]++; } internal static bool GenerateTimelineAssetStats(out timeline_asset_stats data) { var timeline = TimelineEditor.inspectedAsset; if (timeline == null || !AssetDatabase.TryGetGUIDAndLocalFileIdentifier(timeline, out var guid, out long _)) { data = new timeline_asset_stats(); return false; } data = new timeline_asset_stats { asset_guid = guid, duration = timeline.duration, frame_rate = timeline.editorSettings.frameRate, track_stats = GetTrackAssetStats(timeline), display_format = TimelinePreferences.instance.timeFormat.ConvertToString(), mix_samples_count = analyticsStats.editModeSamples[(int)EditMode.EditType.Mix], ripple_samples_count = analyticsStats.editModeSamples[(int)EditMode.EditType.Ripple], replace_samples_count = analyticsStats.editModeSamples[(int)EditMode.EditType.Replace], }; return true; } static List GetTrackAssetStats(TimelineAsset timeline) { var ret = new List(); foreach (var track in timeline.flattenedTracks) { ret.Add(new track_asset_stats { track_type = track.GetType().FullName, clip_count = track.GetClips().Count(), marker_count = track.GetMarkers().Count() } ); } return ret; } [Conditional("ANALYTICS_DEBUG")] static void LogAnalyticsData(timeline_asset_stats data) { UnityEngine.Debug.Log(UnityEngine.JsonUtility.ToJson(data, true)); } } static class ConversionUtilities { internal static string ConvertToString(this T e) where T : Enum { return Enum.GetName(typeof(T), e).ToSnakeCase(); } static string ToSnakeCase(this string str) { var sb = new StringBuilder(); for (var i = 0; i < str.Length - 1; ++i) { var ch = str[i]; var nCh = str[i + 1]; if (char.IsUpper(ch) && char.IsLower(nCh)) { sb.Append("_"); } sb.Append(ch.ToString().ToLower()); } sb.Append(str[str.Length - 1].ToString().ToLower()); return sb.ToString().TrimStart('_'); } } }