PO/Library/PackageCache/com.unity.2d.animation@5.0.7/Editor/Analytics.cs

606 lines
22 KiB
C#

#define WRITE_TO_JSON
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine.Analytics;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
enum AnimationToolType
{
UnknownTool = 0,
Visibilility = 6,
PreviewPose = 7,
EditPose = 8,
CreateBone = 9,
SplitBone = 10,
ReparentBone = 11,
EditGeometry = 12,
CreateVertex = 13,
CreateEdge = 14,
SplitEdge = 15,
GenerateGeometry = 16,
WeightSlider = 17,
WeightBrush = 18,
BoneInfluence = 19,
GenerateWeights = 20
}
[Serializable]
enum AnimationEventType
{
Truncated = -1,
SelectedSpriteChanged = 0,
SkeletonPreviewPoseChanged = 1,
SkeletonBindPoseChanged = 2,
SkeletonTopologyChanged = 3,
MeshChanged = 4,
MeshPreviewChanged = 5,
SkinningModuleModeChanged = 6,
BoneSelectionChanged = 7,
BoneNameChanged = 8,
CharacterPartChanged = 9,
ToolChanged = 10,
RestoreBindPose = 11,
Copy = 12,
Paste = 13,
BoneDepthChanged = 14,
Shortcut = 15,
Visibility = 16
}
[Serializable]
struct AnimationEvent
{
[SerializeField]
public AnimationEventType sub_type;
[SerializeField]
public int repeated_event;
[SerializeField]
public string data;
}
[Serializable]
struct AnimationToolUsageEvent
{
[SerializeField]
public int instance_id;
[SerializeField]
public AnimationToolType animation_tool;
[SerializeField]
public bool character_mode;
[SerializeField]
public int time_start_s;
[SerializeField]
public int time_end_s;
[SerializeField]
public List<AnimationEvent> animation_events;
}
[Serializable]
struct AnimationToolApplyEvent
{
[SerializeField]
public bool character_mode;
[SerializeField]
public int instance_id;
[SerializeField]
public int sprite_count;
[SerializeField]
public int[] bone_sprite_count;
[SerializeField]
public int[] bone_count;
[SerializeField]
public int[] bone_depth;
[SerializeField]
public int[] bone_chain_count;
[SerializeField]
public int bone_root_count;
}
internal interface IAnimationAnalyticsModel
{
bool hasCharacter { get; }
SkinningMode mode { get; }
ITool selectedTool { get; }
ITool GetTool(Tools tool);
int selectedBoneCount { get; }
int applicationElapseTime { get; }
}
internal class SkinningModuleAnalyticsModel : IAnimationAnalyticsModel
{
public SkinningCache skinningCache { get; private set; }
public bool hasCharacter { get { return skinningCache.hasCharacter; } }
public SkinningMode mode { get { return skinningCache.mode; } }
public ITool selectedTool { get { return skinningCache.selectedTool; } }
public SkinningModuleAnalyticsModel(SkinningCache s)
{
skinningCache = s;
}
public ITool GetTool(Tools tool)
{
return skinningCache.GetTool(tool);
}
public int selectedBoneCount { get { return skinningCache.skeletonSelection.Count; } }
public int applicationElapseTime { get { return (int)EditorApplication.timeSinceStartup; } }
}
[Serializable]
internal class AnimationAnalytics
{
const int k_AnimationEventElementCount = 3;
const int k_AnimationToolUsageEventElementCount = 6;
IAnalyticsStorage m_AnalyticsStorage;
SkinningEvents m_EventBus;
IAnimationAnalyticsModel m_Model;
AnimationToolUsageEvent? m_CurrentEvent;
int m_InstanceId;
public AnimationAnalytics(IAnalyticsStorage analyticsStorage, SkinningEvents eventBus, IAnimationAnalyticsModel model, int instanceId)
{
m_Model = model;
m_AnalyticsStorage = analyticsStorage;
m_InstanceId = instanceId;
m_EventBus = eventBus;
m_EventBus.selectedSpriteChanged.AddListener(OnSelectedSpriteChanged);
m_EventBus.skeletonPreviewPoseChanged.AddListener(OnSkeletonPreviewPoseChanged);
m_EventBus.skeletonBindPoseChanged.AddListener(OnSkeletonBindPoseChanged);
m_EventBus.skeletonTopologyChanged.AddListener(OnSkeletonTopologyChanged);
m_EventBus.meshChanged.AddListener(OnMeshChanged);
m_EventBus.meshPreviewChanged.AddListener(OnMeshPreviewChanged);
m_EventBus.skinningModeChanged.AddListener(OnSkinningModuleModeChanged);
m_EventBus.boneSelectionChanged.AddListener(OnBoneSelectionChanged);
m_EventBus.boneNameChanged.AddListener(OnBoneNameChanged);
m_EventBus.boneDepthChanged.AddListener(OnBoneDepthChanged);
m_EventBus.characterPartChanged.AddListener(OnCharacterPartChanged);
m_EventBus.toolChanged.AddListener(OnToolChanged);
m_EventBus.restoreBindPose.AddListener(OnRestoreBindPose);
m_EventBus.copy.AddListener(OnCopy);
m_EventBus.paste.AddListener(OnPaste);
m_EventBus.shortcut.AddListener(OnShortcut);
m_EventBus.boneVisibility.AddListener(OnBoneVisibility);
OnToolChanged(model.selectedTool);
}
public void Dispose()
{
m_EventBus.selectedSpriteChanged.RemoveListener(OnSelectedSpriteChanged);
m_EventBus.skeletonPreviewPoseChanged.RemoveListener(OnSkeletonPreviewPoseChanged);
m_EventBus.skeletonBindPoseChanged.RemoveListener(OnSkeletonBindPoseChanged);
m_EventBus.skeletonTopologyChanged.RemoveListener(OnSkeletonTopologyChanged);
m_EventBus.meshChanged.RemoveListener(OnMeshChanged);
m_EventBus.meshPreviewChanged.RemoveListener(OnMeshPreviewChanged);
m_EventBus.skinningModeChanged.RemoveListener(OnSkinningModuleModeChanged);
m_EventBus.boneSelectionChanged.RemoveListener(OnBoneSelectionChanged);
m_EventBus.boneNameChanged.RemoveListener(OnBoneNameChanged);
m_EventBus.boneDepthChanged.AddListener(OnBoneDepthChanged);
m_EventBus.characterPartChanged.RemoveListener(OnCharacterPartChanged);
m_EventBus.toolChanged.RemoveListener(OnToolChanged);
m_EventBus.copy.RemoveListener(OnCopy);
m_EventBus.paste.RemoveListener(OnPaste);
m_EventBus.shortcut.RemoveListener(OnShortcut);
m_EventBus.boneVisibility.RemoveListener(OnBoneVisibility);
m_AnalyticsStorage.Dispose();
}
void OnBoneVisibility(string s)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.Visibility,
data = s
});
}
void OnShortcut(string s)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.Shortcut,
data = s
});
}
void OnCopy()
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.Copy,
data = ""
});
}
void OnPaste(bool bone , bool mesh , bool flipX , bool flipY)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.Paste,
data = string.Format("b:{0} m:{1} x:{2} y:{3}", bone, mesh, flipX, flipY)
});
}
void OnSelectedSpriteChanged(SpriteCache sprite)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.SelectedSpriteChanged,
data = sprite == null ? "false" : "true"
});
}
void OnSkeletonPreviewPoseChanged(SkeletonCache skeleton)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.SkeletonPreviewPoseChanged,
data = ""
});
}
void OnSkeletonBindPoseChanged(SkeletonCache skeleton)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.SkeletonBindPoseChanged,
data = ""
});
}
void OnSkeletonTopologyChanged(SkeletonCache skeleton)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.SkeletonTopologyChanged,
data = ""
});
}
void OnMeshChanged(MeshCache mesh)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.MeshChanged,
data = ""
});
}
void OnMeshPreviewChanged(MeshPreviewCache mesh)
{
}
void OnSkinningModuleModeChanged(SkinningMode mode)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.SkinningModuleModeChanged,
data = mode.ToString()
});
}
void OnBoneSelectionChanged()
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.BoneSelectionChanged,
data = m_Model.selectedBoneCount.ToString()
});
}
void OnBoneNameChanged(BoneCache bone)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.BoneNameChanged,
data = ""
});
}
void OnBoneDepthChanged(BoneCache bone)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.BoneDepthChanged,
data = ""
});
}
void OnCharacterPartChanged(CharacterPartCache part)
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.CharacterPartChanged,
data = ""
});
}
void OnToolChanged(ITool tool)
{
if (tool == m_Model.GetTool(Tools.ReparentBone))
StartNewEvent(AnimationToolType.ReparentBone, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.CreateBone))
StartNewEvent(AnimationToolType.CreateBone, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.EditJoints))
StartNewEvent(AnimationToolType.EditPose, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.EditPose))
StartNewEvent(AnimationToolType.PreviewPose, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.SplitBone))
StartNewEvent(AnimationToolType.SplitBone, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.CreateEdge))
StartNewEvent(AnimationToolType.CreateEdge, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.CreateVertex))
StartNewEvent(AnimationToolType.CreateVertex, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.EditGeometry))
StartNewEvent(AnimationToolType.EditGeometry, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.GenerateGeometry))
StartNewEvent(AnimationToolType.GenerateGeometry, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.SplitEdge))
StartNewEvent(AnimationToolType.SplitEdge, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.Visibility))
StartNewEvent(AnimationToolType.Visibilility, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.BoneInfluence))
StartNewEvent(AnimationToolType.BoneInfluence, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.GenerateWeights))
StartNewEvent(AnimationToolType.GenerateWeights, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.WeightBrush))
StartNewEvent(AnimationToolType.WeightBrush, m_Model.applicationElapseTime);
else if (tool == m_Model.GetTool(Tools.WeightSlider))
StartNewEvent(AnimationToolType.WeightSlider, m_Model.applicationElapseTime);
else
StartNewEvent(AnimationToolType.UnknownTool, m_Model.applicationElapseTime);
}
void OnRestoreBindPose()
{
SetAnimationEvent(new AnimationEvent()
{
sub_type = AnimationEventType.RestoreBindPose,
data = ""
});
}
void SetAnimationEvent(AnimationEvent evt)
{
if (m_CurrentEvent != null)
{
var toolEvent = m_CurrentEvent.Value;
var eventCount = toolEvent.animation_events.Count;
if (eventCount > 0 && toolEvent.animation_events[eventCount - 1].sub_type == evt.sub_type && toolEvent.animation_events[eventCount - 1].data == evt.data)
{
var e = toolEvent.animation_events[eventCount - 1];
e.repeated_event += 1;
toolEvent.animation_events[eventCount - 1] = e;
}
else
{
var elementCountPlus = k_AnimationToolUsageEventElementCount + (eventCount + 1 * k_AnimationEventElementCount);
if (elementCountPlus >= AnalyticConstant.k_MaxNumberOfElements)
{
// We reached the max number of events. Change the last one to truncated
var e = toolEvent.animation_events[eventCount - 1];
if (e.sub_type != AnimationEventType.Truncated)
{
e.sub_type = AnimationEventType.Truncated;
e.repeated_event = 0;
}
e.repeated_event += 1;
toolEvent.animation_events[eventCount - 1] = e;
}
else
toolEvent.animation_events.Add(evt);
}
m_CurrentEvent = toolEvent;
}
}
void StartNewEvent(AnimationToolType animationType, int tick)
{
SendLastEvent(tick);
m_CurrentEvent = new AnimationToolUsageEvent()
{
instance_id = m_InstanceId,
character_mode = m_Model.mode == SkinningMode.Character,
animation_tool = animationType,
time_start_s = tick,
animation_events = new List<AnimationEvent>()
};
}
void SendLastEvent(AnimationToolUsageEvent evt, int tick)
{
evt.time_end_s = tick;
m_AnalyticsStorage.SendUsageEvent(evt);
}
void SendLastEvent(int tick)
{
if (m_CurrentEvent != null)
{
SendLastEvent(m_CurrentEvent.Value, tick);
}
m_CurrentEvent = null;
}
public void FlushEvent()
{
SendLastEvent(m_Model.applicationElapseTime);
}
public void SendApplyEvent(int spriteCount, int[] spriteBoneCount, BoneCache[] bones)
{
int[] chainBoneCount = null;
int[] maxDepth = null;
int[] boneCount = null;
int boneRootCount = 0;
GetChainBoneStatistic(bones, out chainBoneCount, out maxDepth, out boneRootCount, out boneCount);
var applyEvent = new AnimationToolApplyEvent()
{
instance_id = m_InstanceId,
character_mode = m_Model.hasCharacter,
sprite_count = spriteCount,
bone_sprite_count = spriteBoneCount,
bone_depth = maxDepth,
bone_chain_count = chainBoneCount,
bone_root_count = boneRootCount,
bone_count = boneCount
};
m_AnalyticsStorage.SendApplyEvent(applyEvent);
}
static void GetChainBoneStatistic(BoneCache[] bones, out int[] chainBoneCount, out int[] maxDepth, out int boneRootCount, out int[] boneCount)
{
List<int> chainCountList = new List<int>();
List<int> boneDepthList = new List<int>();
List<int> countList = new List<int>();
boneRootCount = 0;
foreach (var b in bones)
{
if (b.parentBone == null)
{
++boneRootCount;
var chain = 0;
var chainDepth = 0;
var tempBone = b;
var count = 1;
while (tempBone != null)
{
++chainDepth;
tempBone = tempBone.chainedChild;
}
foreach (var b1 in bones)
{
// if this bone is part of this root
var parentBone = b1.parentBone;
while (parentBone != null)
{
if (parentBone == b)
{
++count;
// the bone has a parent and the parent bone's chainedChild is not us, means we are a new chain
if (b1.parentBone != null && b1.parentBone.chainedChild != b1)
{
++chain;
var chainDepth1 = 0;
tempBone = b1;
while (tempBone != null)
{
++chainDepth1;
tempBone = tempBone.chainedChild;
}
chainDepth = chainDepth1 > chainDepth ? chainDepth1 : chainDepth;
}
break;
}
parentBone = parentBone.parentBone;
}
}
chainCountList.Add(chain);
boneDepthList.Add(chainDepth);
countList.Add(count);
}
}
chainBoneCount = chainCountList.ToArray();
maxDepth = boneDepthList.ToArray();
boneCount = countList.ToArray();
}
}
internal interface IAnalyticsStorage
{
AnalyticsResult SendUsageEvent(AnimationToolUsageEvent evt);
AnalyticsResult SendApplyEvent(AnimationToolApplyEvent evt);
void Dispose();
}
internal static class AnalyticConstant
{
public const int k_MaxEventsPerHour = 1000;
public const int k_MaxNumberOfElements = 1000;
}
internal class AnalyticsJsonStorage : IAnalyticsStorage
{
[Serializable]
struct AnimationToolEvents
{
[SerializeField]
public List<AnimationToolUsageEvent> events;
[SerializeField]
public AnimationToolApplyEvent applyEvent;
}
AnimationToolEvents m_TotalEvents = new AnimationToolEvents()
{
events = new List<AnimationToolUsageEvent>(),
applyEvent = new AnimationToolApplyEvent()
};
public AnalyticsResult SendUsageEvent(AnimationToolUsageEvent evt)
{
m_TotalEvents.events.Add(evt);
return AnalyticsResult.Ok;
}
public AnalyticsResult SendApplyEvent(AnimationToolApplyEvent evt)
{
m_TotalEvents.applyEvent = evt;
return AnalyticsResult.Ok;
}
public void Dispose()
{
try
{
string file = string.Format("analytics_{0}.json", System.DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss"));
if (System.IO.File.Exists(file))
System.IO.File.Delete(file);
System.IO.File.WriteAllText(file, JsonUtility.ToJson(m_TotalEvents, true));
}
catch (Exception ex)
{
Debug.Log(ex);
}
finally
{
m_TotalEvents.events.Clear();
}
}
}
[InitializeOnLoad]
internal class UnityAnalyticsStorage : IAnalyticsStorage
{
const string k_VendorKey = "unity.2d.animation";
const int k_Version = 1;
static UnityAnalyticsStorage()
{
EditorAnalytics.RegisterEventWithLimit("u2dAnimationToolUsage", AnalyticConstant.k_MaxEventsPerHour, AnalyticConstant.k_MaxNumberOfElements, k_VendorKey, k_Version);
EditorAnalytics.RegisterEventWithLimit("u2dAnimationToolApply", AnalyticConstant.k_MaxEventsPerHour, AnalyticConstant.k_MaxNumberOfElements, k_VendorKey, k_Version);
}
public AnalyticsResult SendUsageEvent(AnimationToolUsageEvent evt)
{
return EditorAnalytics.SendEventWithLimit("u2dAnimationToolUsage", evt, k_Version);
}
public AnalyticsResult SendApplyEvent(AnimationToolApplyEvent evt)
{
return EditorAnalytics.SendEventWithLimit("u2dAnimationToolApply", evt, k_Version);
}
public void Dispose() {}
}
}