A game about forced loneliness, made by TACStudios
at master 12 kB view raw
1#if UNITY_EDITOR 2using System; 3using UnityEditor; 4using UnityEngine.InputSystem.LowLevel; 5using UnityEngine.Serialization; 6 7namespace UnityEngine.InputSystem.Editor 8{ 9 /// <summary> 10 /// Analytics record for tracking engagement with Input Action Asset editor(s). 11 /// </summary> 12#if UNITY_2023_2_OR_NEWER 13 [UnityEngine.Analytics.AnalyticInfo(eventName: kEventName, maxEventsPerHour: kMaxEventsPerHour, 14 maxNumberOfElements: kMaxNumberOfElements, vendorKey: UnityEngine.InputSystem.InputAnalytics.kVendorKey)] 15#endif // UNITY_2023_2_OR_NEWER 16 internal class InputActionsEditorSessionAnalytic : UnityEngine.InputSystem.InputAnalytics.IInputAnalytic 17 { 18 public const string kEventName = "input_actionasset_editor_closed"; 19 public const int kMaxEventsPerHour = 100; // default: 1000 20 public const int kMaxNumberOfElements = 100; // default: 1000 21 22 /// <summary> 23 /// Construct a new <c>InputActionsEditorSession</c> record of the given <para>type</para>. 24 /// </summary> 25 /// <param name="kind">The editor type for which this record is valid.</param> 26 public InputActionsEditorSessionAnalytic(Data.Kind kind) 27 { 28 if (kind == Data.Kind.Invalid) 29 throw new ArgumentException(nameof(kind)); 30 31 Initialize(kind); 32 } 33 34 /// <summary> 35 /// Register that an action map edit has occurred. 36 /// </summary> 37 public void RegisterActionMapEdit() 38 { 39 if (ImplicitFocus()) 40 ++m_Data.action_map_modification_count; 41 } 42 43 /// <summary> 44 /// Register that an action edit has occurred. 45 /// </summary> 46 public void RegisterActionEdit() 47 { 48 if (ImplicitFocus() && ComputeDuration() > 0.5) // Avoid logging actions triggered via UI initialization 49 ++m_Data.action_modification_count; 50 } 51 52 /// <summary> 53 /// Register than a binding edit has occurred. 54 /// </summary> 55 public void RegisterBindingEdit() 56 { 57 if (ImplicitFocus()) 58 ++m_Data.binding_modification_count; 59 } 60 61 /// <summary> 62 /// Register that a control scheme edit has occurred. 63 /// </summary> 64 public void RegisterControlSchemeEdit() 65 { 66 if (ImplicitFocus()) 67 ++m_Data.control_scheme_modification_count; 68 } 69 70 /// <summary> 71 /// Register that the editor has received focus which is expected to reflect that the user 72 /// is currently exploring or editing it. 73 /// </summary> 74 public void RegisterEditorFocusIn() 75 { 76 if (!hasSession || hasFocus) 77 return; 78 79 m_FocusStart = currentTime; 80 } 81 82 /// <summary> 83 /// Register that the editor has lost focus which is expected to reflect that the user currently 84 /// has the attention elsewhere. 85 /// </summary> 86 /// <remarks> 87 /// Calling this method without having an ongoing session and having focus will not have any effect. 88 /// </remarks> 89 public void RegisterEditorFocusOut() 90 { 91 if (!hasSession || !hasFocus) 92 return; 93 94 var duration = currentTime - m_FocusStart; 95 m_FocusStart = float.NaN; 96 m_Data.session_focus_duration_seconds += (float)duration; 97 ++m_Data.session_focus_switch_count; 98 } 99 100 /// <summary> 101 /// Register a user-event related to explicitly saving in the editor, e.g. 102 /// using a button, menu or short-cut to trigger the save command. 103 /// </summary> 104 public void RegisterExplicitSave() 105 { 106 if (!hasSession) 107 return; // No pending session 108 109 ++m_Data.explicit_save_count; 110 } 111 112 /// <summary> 113 /// Register a user-event related to implicitly saving in the editor, e.g. 114 /// by having auto-save enabled and indirectly saving the associated asset. 115 /// </summary> 116 public void RegisterAutoSave() 117 { 118 if (!hasSession) 119 return; // No pending session 120 121 ++m_Data.auto_save_count; 122 } 123 124 /// <summary> 125 /// Register a user-event related to resetting the editor action configuration to defaults. 126 /// </summary> 127 public void RegisterReset() 128 { 129 if (!hasSession) 130 return; // No pending session 131 132 ++m_Data.reset_count; 133 } 134 135 /// <summary> 136 /// Begins a new session if the session has not already been started. 137 /// </summary> 138 /// <remarks> 139 /// If the session has already been started due to a previous call to <see cref="Begin()"/> without 140 /// a call to <see cref="End()"/> this method has no effect. 141 /// </remarks> 142 public void Begin() 143 { 144 if (hasSession) 145 return; // Session already started. 146 147 m_SessionStart = currentTime; 148 } 149 150 /// <summary> 151 /// Ends the current session. 152 /// </summary> 153 /// <remarks> 154 /// If the session has not previously been started via a call to <see cref="Begin()"/> calling this 155 /// method has no effect. 156 /// </remarks> 157 public void End() 158 { 159 if (!hasSession) 160 return; // No pending session 161 162 // Make sure we register focus out if failed to capture or not invoked 163 if (hasFocus) 164 RegisterEditorFocusOut(); 165 166 // Compute and record total session duration 167 var duration = ComputeDuration(); 168 m_Data.session_duration_seconds += duration; 169 170 // Sanity check data, if less than a second its likely a glitch so avoid sending incorrect data 171 // Send analytics event 172 if (duration >= 1.0) 173 runtime.SendAnalytic(this); 174 175 // Reset to allow instance to be reused 176 Initialize(m_Data.kind); 177 } 178 179 #region IInputAnalytic Interface 180 181#if UNITY_2023_2_OR_NEWER 182 public bool TryGatherData(out UnityEngine.Analytics.IAnalytic.IData data, out Exception error) 183#else 184 public bool TryGatherData(out InputAnalytics.IInputAnalyticData data, out Exception error) 185#endif 186 { 187 if (!isValid) 188 { 189 data = null; 190 error = new Exception("Unable to gather data without a valid session"); 191 return false; 192 } 193 194 data = this.m_Data; 195 error = null; 196 return true; 197 } 198 199 public InputAnalytics.InputAnalyticInfo info => new InputAnalytics.InputAnalyticInfo(kEventName, kMaxEventsPerHour, kMaxNumberOfElements); 200 201 #endregion 202 203 private double ComputeDuration() => hasSession ? currentTime - m_SessionStart : 0.0; 204 205 private void Initialize(Data.Kind kind) 206 { 207 m_FocusStart = float.NaN; 208 m_SessionStart = float.NaN; 209 210 m_Data = new Data(kind); 211 } 212 213 private bool ImplicitFocus() 214 { 215 if (!hasSession) 216 return false; 217 if (!hasFocus) 218 RegisterEditorFocusIn(); 219 return true; 220 } 221 222 private Data m_Data; 223 private double m_FocusStart; 224 private double m_SessionStart; 225 226 private static IInputRuntime runtime => InputSystem.s_Manager.m_Runtime; 227 private bool hasFocus => !double.IsNaN(m_FocusStart); 228 private bool hasSession => !double.IsNaN(m_SessionStart); 229 // Returns current time since startup. Note that IInputRuntime explicitly defines in interface that 230 // IInputRuntime.currentTime corresponds to EditorApplication.timeSinceStartup in editor. 231 private double currentTime => runtime.currentTime; 232 private bool isValid => m_Data.session_duration_seconds >= 0; 233 234 [Serializable] 235 public struct Data : UnityEngine.InputSystem.InputAnalytics.IInputAnalyticData 236 { 237 /// <summary> 238 /// Represents an editor type. 239 /// </summary> 240 /// <remarks> 241 /// This may be added to in the future but items may never be removed. 242 /// </remarks> 243 [Serializable] 244 public enum Kind 245 { 246 Invalid = 0, 247 EditorWindow = 1, 248 EmbeddedInProjectSettings = 2 249 } 250 251 /// <summary> 252 /// Constructs a <c>InputActionsEditorSessionData</c>. 253 /// </summary> 254 /// <param name="kind">Specifies the kind of editor metrics is being collected for.</param> 255 public Data(Kind kind) 256 { 257 this.kind = kind; 258 session_duration_seconds = 0; 259 session_focus_duration_seconds = 0; 260 session_focus_switch_count = 0; 261 action_map_modification_count = 0; 262 action_modification_count = 0; 263 binding_modification_count = 0; 264 explicit_save_count = 0; 265 auto_save_count = 0; 266 reset_count = 0; 267 control_scheme_modification_count = 0; 268 } 269 270 /// <summary> 271 /// Specifies what kind of Input Actions editor this event represents. 272 /// </summary> 273 public Kind kind; 274 275 /// <summary> 276 /// The total duration for the session, i.e. the duration during which the editor window was open. 277 /// </summary> 278 public double session_duration_seconds; 279 280 /// <summary> 281 /// The total duration for which the editor window was open and had focus. 282 /// </summary> 283 public double session_focus_duration_seconds; 284 285 /// <summary> 286 /// Specifies the number of times the window has transitioned from not having focus to having focus in a single session. 287 /// </summary> 288 public int session_focus_switch_count; 289 290 /// <summary> 291 /// The total number of action map modifications during the session. 292 /// </summary> 293 public int action_map_modification_count; 294 295 /// <summary> 296 /// The total number of action modifications during the session. 297 /// </summary> 298 public int action_modification_count; 299 300 /// The total number of binding modifications during the session. 301 /// </summary> 302 public int binding_modification_count; 303 304 /// <summary> 305 /// The total number of controls scheme modifications during the session. 306 /// </summary> 307 public int control_scheme_modification_count; 308 309 /// <summary> 310 /// The total number of explicit saves during the session, i.e. as in user-initiated save. 311 /// </summary> 312 public int explicit_save_count; 313 314 /// <summary> 315 /// The total number of automatic saves during the session, i.e. as in auto-save on close or focus-lost. 316 /// </summary> 317 public int auto_save_count; 318 319 /// <summary> 320 /// The total number of user-initiated resets during the session, i.e. as in using Reset option in menu. 321 /// </summary> 322 public int reset_count; 323 324 public bool isValid => kind != Kind.Invalid && session_duration_seconds >= 0; 325 } 326 } 327} 328#endif