TransitionLibrarySort.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using Animancer.TransitionLibraries;
  4. using System;
  5. using System.Collections.Generic;
  6. using UnityEditor;
  7. using UnityEngine;
  8. namespace Animancer.Editor.TransitionLibraries
  9. {
  10. /// <summary>[Editor-Only] Utility for sorting a <see cref="TransitionLibraryAsset"/>.</summary>
  11. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySort
  12. public class TransitionLibrarySort : AssetModificationProcessor
  13. {
  14. /************************************************************************************************************************/
  15. #region Automation
  16. /************************************************************************************************************************/
  17. /// <summary>Ensures that a <see cref="TransitionLibraryAsset"/> is sorted before being saved.</summary>
  18. private static string[] OnWillSaveAssets(string[] paths)
  19. {
  20. foreach (var path in paths)
  21. {
  22. if (!path.EndsWith(".asset", StringComparison.Ordinal))
  23. continue;
  24. var library = AssetDatabase.LoadAssetAtPath<TransitionLibraryAsset>(path);
  25. if (library == null)
  26. continue;
  27. Sort(library);
  28. }
  29. return paths;
  30. }
  31. /************************************************************************************************************************/
  32. #endregion
  33. /************************************************************************************************************************/
  34. #region Sort Modes
  35. /************************************************************************************************************************/
  36. /// <summary>Applies the <see cref="TransitionLibraryEditorData.TransitionSortMode"/>.</summary>
  37. public static void Sort(TransitionLibraryAsset library)
  38. {
  39. // Can't have editor data if not an asset, so the sort mode will be custom anyway.
  40. if (!AssetDatabase.Contains(library))
  41. return;
  42. var data = library.GetOrCreateEditorData();
  43. if (data.TransitionSortMode == TransitionSortMode.Custom)
  44. return;
  45. NameCache.Clear();
  46. switch (data.TransitionSortMode)
  47. {
  48. case TransitionSortMode.Name:
  49. Sort(library.Definition, Static<CompareName>.Instance);
  50. break;
  51. case TransitionSortMode.Path:
  52. Sort(library.Definition, Static<ComparePath>.Instance);
  53. break;
  54. case TransitionSortMode.TypeThenName:
  55. Sort(library.Definition, Static<CompareTypeThenName>.Instance);
  56. break;
  57. case TransitionSortMode.TypeThenPath:
  58. Sort(library.Definition, Static<CompareTypeThenPath>.Instance);
  59. break;
  60. }
  61. }
  62. /************************************************************************************************************************/
  63. /// <summary>Compares the asset names then GUIDs.</summary>
  64. private class CompareName : IComparer<TransitionAssetBase>
  65. {
  66. public int Compare(TransitionAssetBase a, TransitionAssetBase b)
  67. {
  68. var result = CompareNulls(a, b);
  69. if (result != 0)
  70. return result;
  71. result = CompareCachedNames(a, b);
  72. if (result != 0)
  73. return result;
  74. return CompareGUIDs(a, b);
  75. }
  76. }
  77. /************************************************************************************************************************/
  78. /// <summary>Compares the asset paths then GUIDs.</summary>
  79. private class ComparePath : IComparer<TransitionAssetBase>
  80. {
  81. public int Compare(TransitionAssetBase a, TransitionAssetBase b)
  82. {
  83. var result = CompareNulls(a, b);
  84. if (result != 0)
  85. return result;
  86. result = ComparePaths(a, b);
  87. if (result != 0)
  88. return result;
  89. result = CompareCachedNames(a, b);
  90. if (result != 0)
  91. return result;
  92. return CompareGUIDs(a, b);
  93. }
  94. }
  95. /************************************************************************************************************************/
  96. /// <summary>Compares the transition types then asset names then GUIDs.</summary>
  97. private class CompareTypeThenName : IComparer<TransitionAssetBase>
  98. {
  99. public int Compare(TransitionAssetBase a, TransitionAssetBase b)
  100. {
  101. var result = CompareNulls(a, b);
  102. if (result != 0)
  103. return result;
  104. result = CompareTypes(a, b);
  105. if (result != 0)
  106. return result;
  107. result = CompareCachedNames(a, b);
  108. if (result != 0)
  109. return result;
  110. return CompareGUIDs(a, b);
  111. }
  112. }
  113. /************************************************************************************************************************/
  114. /// <summary>Compares the transition types then asset paths then GUIDs.</summary>
  115. private class CompareTypeThenPath : IComparer<TransitionAssetBase>
  116. {
  117. public int Compare(TransitionAssetBase a, TransitionAssetBase b)
  118. {
  119. var result = CompareNulls(a, b);
  120. if (result != 0)
  121. return result;
  122. result = CompareTypes(a, b);
  123. if (result != 0)
  124. return result;
  125. result = ComparePaths(a, b);
  126. if (result != 0)
  127. return result;
  128. result = CompareCachedNames(a, b);
  129. if (result != 0)
  130. return result;
  131. return CompareGUIDs(a, b);
  132. }
  133. }
  134. /************************************************************************************************************************/
  135. /// <summary>Compares objects to put null or destroyed ones at the end.</summary>
  136. private static int CompareNulls(TransitionAssetBase a, TransitionAssetBase b)
  137. => (a == null).CompareTo(b == null);
  138. /// <summary>Compares the asset GUIDs.</summary>
  139. private static int CompareGUIDs(TransitionAssetBase a, TransitionAssetBase b)
  140. {
  141. var gotA = AssetDatabase.TryGetGUIDAndLocalFileIdentifier(a, out var aGUID, out long aLocalID);
  142. var gotB = AssetDatabase.TryGetGUIDAndLocalFileIdentifier(b, out var bGUID, out long bLocalID);
  143. var result = gotA.CompareTo(gotB);
  144. if (result != 0)
  145. return result;
  146. result = aGUID.CompareTo(bGUID);
  147. if (result != 0)
  148. return result;
  149. return aLocalID.CompareTo(bLocalID);
  150. }
  151. /// <summary>Compares the asset names.</summary>
  152. private static int CompareCachedNames(TransitionAssetBase a, TransitionAssetBase b)
  153. => a.GetCachedName().CompareTo(b.GetCachedName());
  154. /// <summary>Compares the asset paths.</summary>
  155. private static int ComparePaths(TransitionAssetBase a, TransitionAssetBase b)
  156. => AssetDatabase.GetAssetPath(a).CompareTo(AssetDatabase.GetAssetPath(b));
  157. /// <summary>Compares the transition types.</summary>
  158. private static int CompareTypes(TransitionAssetBase a, TransitionAssetBase b)
  159. {
  160. if (AnimancerUtilities.TryGetWrappedObject<ITransition>(a, out var transitionA) &&
  161. AnimancerUtilities.TryGetWrappedObject<ITransition>(b, out var transitionB))
  162. {
  163. var result = transitionA.GetType().GetNameCS().CompareTo(transitionB.GetType().GetNameCS());
  164. if (result != 0)
  165. return result;
  166. }
  167. return a.GetType().GetNameCS().CompareTo(b.GetType().GetNameCS());
  168. }
  169. /************************************************************************************************************************/
  170. #endregion
  171. /************************************************************************************************************************/
  172. #region Sorting
  173. /************************************************************************************************************************/
  174. private static TransitionAssetBase[]
  175. _SortingTransitions = Array.Empty<TransitionAssetBase>();
  176. private static int[] _OldIndexToNew;
  177. /************************************************************************************************************************/
  178. /// <summary>Sorts the <see cref="TransitionLibraryDefinition.Transitions"/>.</summary>
  179. public static void Sort(
  180. TransitionLibraryDefinition library,
  181. Comparison<TransitionAssetBase> comparison)
  182. => Sort(library, new Comparison<TransitionAssetBase>(comparison));
  183. /// <summary>Sorts the <see cref="TransitionLibraryDefinition.Transitions"/>.</summary>
  184. public static void Sort(
  185. TransitionLibraryDefinition library,
  186. IComparer<TransitionAssetBase> comparer)
  187. {
  188. var transitions = library.Transitions;
  189. var count = transitions.Length;
  190. if (_SortingTransitions.Length < count)
  191. {
  192. var length = Mathf.NextPowerOfTwo(count);
  193. _SortingTransitions = new TransitionAssetBase[length];
  194. _OldIndexToNew = new int[length];
  195. }
  196. Array.Copy(transitions, _SortingTransitions, count);
  197. // Indices 0 -> Count.
  198. var newIndexToOld = GetTempSequentialIndices(count);
  199. Array.Sort(_SortingTransitions, newIndexToOld, 0, count, comparer);
  200. // Remove nulls which should have been sorted to the end.
  201. for (int i = count - 1; i >= 0; i--)
  202. if (_SortingTransitions[i] == null)
  203. count--;
  204. else
  205. break;
  206. // _NewIndexToOld[x] is now the index that Transitions[x] was at previously.
  207. // We need to invert that so _OldIndexToNew[x] is the new index of whatever was previously at Transitions[x].
  208. // That allows the library to update any index references using a simple x = _OldIndexToNew[x];
  209. for (int i = 0; i < count; i++)
  210. _OldIndexToNew[newIndexToOld[i]] = i;
  211. SetTransitions(library, _SortingTransitions, _OldIndexToNew, count);
  212. }
  213. /************************************************************************************************************************/
  214. /// <summary>
  215. /// Sets the <see cref="TransitionLibraryDefinition.Transitions"/>
  216. /// using `oldIndexToNew` to remap any references to the old order.
  217. /// </summary>
  218. public static void SetTransitions(
  219. TransitionLibraryDefinition library,
  220. TransitionAssetBase[] transitions,
  221. int[] oldIndexToNew,
  222. int count)
  223. {
  224. var libraryTransitions = library.Transitions;
  225. if (libraryTransitions != transitions)
  226. {
  227. AnimancerUtilities.SetLength(ref libraryTransitions, count);
  228. Array.Copy(transitions, libraryTransitions, count);
  229. library.Transitions = libraryTransitions;
  230. }
  231. var modifiers = library.Modifiers;
  232. for (int i = modifiers.Length - 1; i >= 0; i--)
  233. {
  234. var modifier = modifiers[i];
  235. var isValid = true;
  236. var fromIndex = ConvertIndex(modifier.FromIndex, oldIndexToNew, count, ref isValid);
  237. var toIndex = ConvertIndex(modifier.ToIndex, oldIndexToNew, count, ref isValid);
  238. if (isValid)
  239. modifiers[i] = modifier.WithIndices(fromIndex, toIndex);
  240. else
  241. AnimancerUtilities.RemoveAt(ref modifiers, i);
  242. }
  243. var aliases = library.Aliases;
  244. for (int i = aliases.Length - 1; i >= 0; i--)
  245. {
  246. var alias = aliases[i];
  247. var isValid = true;
  248. var index = ConvertIndex(alias.Index, oldIndexToNew, count, ref isValid);
  249. if (isValid)
  250. aliases[i] = alias.With(index);
  251. else
  252. AnimancerUtilities.RemoveAt(ref aliases, i);
  253. }
  254. library.SortAliases();
  255. }
  256. /************************************************************************************************************************/
  257. /// <summary>Converts an old index to a new one.</summary>
  258. private static int ConvertIndex(int index, int[] oldIndexToNew, int count, ref bool isValid)
  259. {
  260. if ((uint)index >= (uint)count)
  261. {
  262. isValid = false;
  263. return -1;
  264. }
  265. index = oldIndexToNew[index];
  266. if ((uint)index >= (uint)count)
  267. {
  268. isValid = false;
  269. return -1;
  270. }
  271. return index;
  272. }
  273. /************************************************************************************************************************/
  274. private static int[] _SequentialIndices = Array.Empty<int>();
  275. /// <summary>Returns a cached array containing sequential indices, i.e. <c>array[i] = i</c>.</summary>
  276. public static int[] GetTempSequentialIndices(int count)
  277. {
  278. if (_SequentialIndices.Length < count)
  279. _SequentialIndices = new int[Mathf.NextPowerOfTwo(count)];
  280. for (int i = 0; i < _SequentialIndices.Length; i++)
  281. _SequentialIndices[i] = i;
  282. return _SequentialIndices;
  283. }
  284. /************************************************************************************************************************/
  285. /// <summary>Changes the index of a transition.</summary>
  286. public static void MoveTransition(TransitionLibraryWindow window, int from, int to)
  287. {
  288. var transitions = window.Data.Transitions;
  289. to = Mathf.Clamp(to, 0, transitions.Length - 1);
  290. if (from == to)
  291. return;
  292. var editorData = window.SourceObject.GetOrCreateEditorData();
  293. var definition = window.RecordUndo();
  294. editorData.TransitionSortMode = TransitionSortMode.Custom;
  295. var moving = transitions[from];
  296. var indices = GetTempSequentialIndices(transitions.Length);
  297. if (to > from)// Moving forwards.
  298. {
  299. Array.Copy(transitions, from + 1, transitions, from, to - from);
  300. Array.Copy(indices, from, indices, from + 1, to - from);
  301. }
  302. else// Moving backwards.
  303. {
  304. Array.Copy(transitions, to, transitions, to + 1, from - to);
  305. Array.Copy(indices, to + 1, indices, to, from - to);
  306. }
  307. transitions[to] = moving;
  308. indices[from] = to;
  309. SetTransitions(
  310. definition,
  311. transitions,
  312. indices,
  313. transitions.Length);
  314. }
  315. /************************************************************************************************************************/
  316. #endregion
  317. /************************************************************************************************************************/
  318. }
  319. }
  320. #endif