312 lines
13 KiB
C#
312 lines
13 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.Analytics;
|
|||
|
|
|||
|
namespace UnityEditor.TerrainTools
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Analytics class for collecting and sending aggregated user data
|
|||
|
/// </summary>
|
|||
|
public static class TerrainToolsAnalytics
|
|||
|
{
|
|||
|
//Event Data
|
|||
|
static bool s_EventRegistered = false;
|
|||
|
const int k_MaxEventsPerHour = 1000;
|
|||
|
const int k_MaxNumberOfElements = 1000;
|
|||
|
const string k_VendorKey = "unity.terraintools";
|
|||
|
const string k_EventName = "analytics.uTerrainTools";
|
|||
|
|
|||
|
//Brush Analytics Data
|
|||
|
const float k_SignficantThreshold = .01f;
|
|||
|
|
|||
|
static BrushAnalyticsData m_Data;
|
|||
|
static List<BrushParameterData> s_ModifiedBrushParameters = new List<BrushParameterData>();
|
|||
|
static List<string> s_UsedBrushShortcut = new List<string>();
|
|||
|
static float s_PaintingDuration;
|
|||
|
static bool s_ParameterChanged;
|
|||
|
|
|||
|
[Serializable]
|
|||
|
struct BrushParameterData
|
|||
|
{
|
|||
|
public string name;
|
|||
|
public string value;
|
|||
|
}
|
|||
|
|
|||
|
struct BrushAnalyticsData
|
|||
|
{
|
|||
|
public string brush_name;
|
|||
|
public List<string> shortcuts;
|
|||
|
public string[] mask_filters;
|
|||
|
public float strength;
|
|||
|
public float size;
|
|||
|
public float rotation;
|
|||
|
public float spacing;
|
|||
|
public float scatter;
|
|||
|
public float duration;
|
|||
|
public List<BrushParameterData> brush_parameters;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Array of BrushParameters used to compare against the most recent brush parameters to check if there
|
|||
|
/// has been a change.
|
|||
|
/// </summary>
|
|||
|
internal static IBrushParameter[] m_OriginalParameters;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Interface for iterating over brush parameters of an ambiguous type.
|
|||
|
/// </summary>
|
|||
|
public interface IBrushParameter
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Retrieves the parameter's type.
|
|||
|
/// </summary>
|
|||
|
/// <returns>Returns the parameter's system type.</returns>
|
|||
|
System.Type ParameterType();
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets and sets the parameters name.
|
|||
|
/// </summary>
|
|||
|
string Name { get; set; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Struct containing the name and value associated to an individual brush parameter.
|
|||
|
/// </summary>
|
|||
|
/// <typeparam name="T">The variable type of the brush parameter</typeparam>
|
|||
|
internal struct BrushParameter<T> : IBrushParameter
|
|||
|
{
|
|||
|
public System.Type ParameterType()
|
|||
|
{
|
|||
|
return Value.GetType();
|
|||
|
}
|
|||
|
public T Value { get; set; }
|
|||
|
public string Name { get; set; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Register the AnalyticsEvent for sending data to BigQuery
|
|||
|
/// </summary>
|
|||
|
/// <returns>EventRegisterd boolean identifying if the event was registered correctly</returns>
|
|||
|
static bool EnableAnalytics()
|
|||
|
{
|
|||
|
//Early out if the event has already been registered, returning bool determining
|
|||
|
//if Editor Analytics are enabled
|
|||
|
if (s_EventRegistered)
|
|||
|
return EditorAnalytics.enabled;
|
|||
|
|
|||
|
AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventName, k_MaxEventsPerHour,
|
|||
|
k_MaxNumberOfElements, k_VendorKey);
|
|||
|
|
|||
|
if (result == AnalyticsResult.Ok)
|
|||
|
s_EventRegistered = true;
|
|||
|
|
|||
|
return s_EventRegistered && EditorAnalytics.enabled;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Update the analytics data to be sent when a user starts painting with new parameters/settings
|
|||
|
/// The users time is being tracked while painting
|
|||
|
/// Once the user changes any brush parameters original data is sent and the new data is cached to be compared later
|
|||
|
/// </summary>
|
|||
|
/// <param name="baseBrushSettings">Brush Base class containing common brush parameters</param>
|
|||
|
/// <param name="brushParamFunc">Function returning brush specific parameters</param>
|
|||
|
internal static void UpdateAnalytics(BaseBrushUIGroup baseBrushSettings, Func<TerrainToolsAnalytics.IBrushParameter[]> brushParamFunc)
|
|||
|
{
|
|||
|
if (!EnableAnalytics())
|
|||
|
return;
|
|||
|
|
|||
|
s_PaintingDuration += Time.deltaTime;
|
|||
|
|
|||
|
if (!s_ParameterChanged)
|
|||
|
return;
|
|||
|
|
|||
|
SendAnalytics();
|
|||
|
CompareBrushSettings(brushParamFunc?.Invoke());
|
|||
|
CacheAnalyticsData(baseBrushSettings);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Send the EditorAnalytics event and clear data for reuse
|
|||
|
/// </summary>
|
|||
|
static void SendAnalytics()
|
|||
|
{
|
|||
|
if (m_Data.Equals(default(BrushAnalyticsData)))
|
|||
|
return;
|
|||
|
|
|||
|
m_Data.duration = s_PaintingDuration;
|
|||
|
EditorAnalytics.SendEventWithLimit(k_EventName, m_Data);
|
|||
|
|
|||
|
//Clear data
|
|||
|
s_ModifiedBrushParameters.Clear();
|
|||
|
s_UsedBrushShortcut.Clear();
|
|||
|
s_ParameterChanged = false;
|
|||
|
s_PaintingDuration = 0;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Checks whether the brush parameters have changed between
|
|||
|
/// the original and current state of a brush.
|
|||
|
/// </summary>
|
|||
|
/// <param name="parameters">Array of brushparameter structs which identify the name and value of the brushes
|
|||
|
/// parameters. </param>
|
|||
|
static void CompareBrushSettings(IBrushParameter[] parameters)
|
|||
|
{
|
|||
|
if (parameters == null)
|
|||
|
return;
|
|||
|
|
|||
|
for (int i = 0; (i < parameters.Length && i < m_OriginalParameters.Length); i++)
|
|||
|
{
|
|||
|
if (parameters[i].Equals(m_OriginalParameters[i]))
|
|||
|
continue;
|
|||
|
|
|||
|
System.Type type = parameters[i].ParameterType();
|
|||
|
TypeCode typecode = Type.GetTypeCode(type);
|
|||
|
switch (typecode)
|
|||
|
{
|
|||
|
case TypeCode.Int32:
|
|||
|
case TypeCode.Int64:
|
|||
|
{
|
|||
|
int currentValue = ((BrushParameter<int>)parameters[i]).Value;
|
|||
|
int originalValue = ((BrushParameter<int>)m_OriginalParameters[i]).Value;
|
|||
|
CacheChangedParamter(parameters[i].Name, currentValue, originalValue);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case TypeCode.Single:
|
|||
|
{
|
|||
|
float currentValue = ((BrushParameter<float>)parameters[i]).Value;
|
|||
|
float originalValue = ((BrushParameter<float>)m_OriginalParameters[i]).Value;
|
|||
|
|
|||
|
//Check if the user made a significant enough change to the parameter
|
|||
|
if (CompareSignificance(currentValue, originalValue) == originalValue)
|
|||
|
{
|
|||
|
break;
|
|||
|
}
|
|||
|
CacheChangedParamter(parameters[i].Name, currentValue, originalValue);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case TypeCode.Boolean:
|
|||
|
{
|
|||
|
bool currentValue = ((BrushParameter<bool>)parameters[i]).Value;
|
|||
|
bool originalValue = ((BrushParameter<bool>)m_OriginalParameters[i]).Value;
|
|||
|
CacheChangedParamter(parameters[i].Name, currentValue, originalValue);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case TypeCode.String:
|
|||
|
{
|
|||
|
string currentValue = ((BrushParameter<string>)parameters[i]).Value;
|
|||
|
string originalValue = ((BrushParameter<string>)m_OriginalParameters[i]).Value;
|
|||
|
CacheChangedParamter(parameters[i].Name, currentValue, originalValue);
|
|||
|
break;
|
|||
|
}
|
|||
|
case TypeCode.Object:
|
|||
|
{
|
|||
|
if (type == typeof(Vector3))
|
|||
|
{
|
|||
|
Vector3 currentValue = ((BrushParameter<Vector3>)parameters[i]).Value;
|
|||
|
Vector3 originalValue = ((BrushParameter<Vector3>)m_OriginalParameters[i]).Value;
|
|||
|
CacheChangedParamter(parameters[i].Name, currentValue, originalValue);
|
|||
|
}
|
|||
|
else if (type == typeof(Vector4))
|
|||
|
{
|
|||
|
Vector4 currentValue = ((BrushParameter<Vector4>)parameters[i]).Value;
|
|||
|
Vector4 originalValue = ((BrushParameter<Vector4>)m_OriginalParameters[i]).Value;
|
|||
|
CacheChangedParamter(parameters[i].Name, currentValue, originalValue);
|
|||
|
}
|
|||
|
else if (type == typeof(Keyframe[]))
|
|||
|
{
|
|||
|
Keyframe[] currentValue = ((BrushParameter<Keyframe[]>)parameters[i]).Value;
|
|||
|
Keyframe[] originalValue = ((BrushParameter<Keyframe[]>)m_OriginalParameters[i]).Value;
|
|||
|
for (int k = 0; k < currentValue.Length; k++)
|
|||
|
{
|
|||
|
//Check if there's a change between the original and current keyframe values
|
|||
|
//Cache the change if there's a difference
|
|||
|
if (!currentValue[k].Equals(originalValue[k]))
|
|||
|
{
|
|||
|
CacheChangedParamter($"{parameters[i].Name}", currentValue[k], originalValue[k]);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
default:
|
|||
|
Debug.LogWarning($"The parameter of type {type} isn't able to be tracked by Analytics");
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
m_OriginalParameters = parameters;
|
|||
|
}
|
|||
|
|
|||
|
static void CacheAnalyticsData(BaseBrushUIGroup brush)
|
|||
|
{
|
|||
|
m_Data.brush_name = brush.brushName;
|
|||
|
m_Data.shortcuts = s_UsedBrushShortcut;
|
|||
|
m_Data.mask_filters = brush.brushMaskFilterStack.filters?.
|
|||
|
Where(x => x.enabled).
|
|||
|
Select(x => x.GetType().Name).ToArray();
|
|||
|
m_Data.strength = CompareSignificance(brush.brushStrength, m_Data.strength);
|
|||
|
m_Data.size = CompareSignificance(brush.brushSize, m_Data.size);
|
|||
|
m_Data.rotation = CompareSignificance(brush.brushRotation, m_Data.rotation);
|
|||
|
m_Data.spacing = CompareSignificance(brush.brushSpacing, m_Data.spacing);
|
|||
|
m_Data.scatter = CompareSignificance(brush.brushScatter, m_Data.scatter);
|
|||
|
m_Data.brush_parameters = s_ModifiedBrushParameters;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Caches the shortcutId on keyRelease to be sent as analytics data if the
|
|||
|
/// shortcut key hasn't been cached already
|
|||
|
/// </summary>
|
|||
|
/// <param name="shortcutId">ID of the shortcut </param>
|
|||
|
internal static void OnShortcutKeyRelease(string shortcutId)
|
|||
|
{
|
|||
|
if (!s_UsedBrushShortcut.Contains(shortcutId))
|
|||
|
s_UsedBrushShortcut.Add(shortcutId);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Flags the parameter changed boolean notifying that the user has changed brush parameters
|
|||
|
/// and the old data needs to be sent while the new data needs to be cached.
|
|||
|
/// </summary>
|
|||
|
internal static void OnParameterChange() => s_ParameterChanged = true;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Cache the parameter that has been changed to be sent as analytics data
|
|||
|
/// </summary>
|
|||
|
/// <param name="name">The name of the changed setting Ex: "Brush Strength"</param>
|
|||
|
/// <param name="currentSetting">Setting of the brush before the user starts painting</param>
|
|||
|
/// <param name="originalSetting">Setting of the brush which the user starts painting with</param>
|
|||
|
/// <returns>Returns a boolean indicating if the parameter was changed</returns>
|
|||
|
static void CacheChangedParamter<T>(string name, T currentSetting, T originalSetting)
|
|||
|
{
|
|||
|
s_ModifiedBrushParameters.Add(new BrushParameterData
|
|||
|
{
|
|||
|
name = name,
|
|||
|
value = currentSetting.ToString()
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Check whether the difference between the initial and current brush parameters
|
|||
|
/// is significant enough to send the data for analyzing.
|
|||
|
/// </summary>
|
|||
|
/// <param name="currentValue">The latest brush parameter</param>
|
|||
|
/// <param name="originalValue">The starting brush parameter</param>
|
|||
|
/// <returns></returns>
|
|||
|
static float CompareSignificance(float currentValue, float originalValue)
|
|||
|
{
|
|||
|
//Determine if value A has significantly changed from value B
|
|||
|
if (Mathf.Abs(currentValue - originalValue) >= k_SignficantThreshold)
|
|||
|
return currentValue;
|
|||
|
else
|
|||
|
return originalValue;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|