// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // #if UNITY_EDITOR using Animancer.TransitionLibraries; using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace Animancer.Editor.TransitionLibraries { /// [Editor-Only] Utility for sorting a . /// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySort public class TransitionLibrarySort : AssetModificationProcessor { /************************************************************************************************************************/ #region Automation /************************************************************************************************************************/ /// Ensures that a is sorted before being saved. private static string[] OnWillSaveAssets(string[] paths) { foreach (var path in paths) { if (!path.EndsWith(".asset", StringComparison.Ordinal)) continue; var library = AssetDatabase.LoadAssetAtPath(path); if (library == null) continue; Sort(library); } return paths; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Sort Modes /************************************************************************************************************************/ /// Applies the . public static void Sort(TransitionLibraryAsset library) { // Can't have editor data if not an asset, so the sort mode will be custom anyway. if (!AssetDatabase.Contains(library)) return; var data = library.GetOrCreateEditorData(); if (data.TransitionSortMode == TransitionSortMode.Custom) return; NameCache.Clear(); switch (data.TransitionSortMode) { case TransitionSortMode.Name: Sort(library.Definition, Static.Instance); break; case TransitionSortMode.Path: Sort(library.Definition, Static.Instance); break; case TransitionSortMode.TypeThenName: Sort(library.Definition, Static.Instance); break; case TransitionSortMode.TypeThenPath: Sort(library.Definition, Static.Instance); break; } } /************************************************************************************************************************/ /// Compares the asset names then GUIDs. private class CompareName : IComparer { public int Compare(TransitionAssetBase a, TransitionAssetBase b) { var result = CompareNulls(a, b); if (result != 0) return result; result = CompareCachedNames(a, b); if (result != 0) return result; return CompareGUIDs(a, b); } } /************************************************************************************************************************/ /// Compares the asset paths then GUIDs. private class ComparePath : IComparer { public int Compare(TransitionAssetBase a, TransitionAssetBase b) { var result = CompareNulls(a, b); if (result != 0) return result; result = ComparePaths(a, b); if (result != 0) return result; result = CompareCachedNames(a, b); if (result != 0) return result; return CompareGUIDs(a, b); } } /************************************************************************************************************************/ /// Compares the transition types then asset names then GUIDs. private class CompareTypeThenName : IComparer { public int Compare(TransitionAssetBase a, TransitionAssetBase b) { var result = CompareNulls(a, b); if (result != 0) return result; result = CompareTypes(a, b); if (result != 0) return result; result = CompareCachedNames(a, b); if (result != 0) return result; return CompareGUIDs(a, b); } } /************************************************************************************************************************/ /// Compares the transition types then asset paths then GUIDs. private class CompareTypeThenPath : IComparer { public int Compare(TransitionAssetBase a, TransitionAssetBase b) { var result = CompareNulls(a, b); if (result != 0) return result; result = CompareTypes(a, b); if (result != 0) return result; result = ComparePaths(a, b); if (result != 0) return result; result = CompareCachedNames(a, b); if (result != 0) return result; return CompareGUIDs(a, b); } } /************************************************************************************************************************/ /// Compares objects to put null or destroyed ones at the end. private static int CompareNulls(TransitionAssetBase a, TransitionAssetBase b) => (a == null).CompareTo(b == null); /// Compares the asset GUIDs. private static int CompareGUIDs(TransitionAssetBase a, TransitionAssetBase b) { var gotA = AssetDatabase.TryGetGUIDAndLocalFileIdentifier(a, out var aGUID, out long aLocalID); var gotB = AssetDatabase.TryGetGUIDAndLocalFileIdentifier(b, out var bGUID, out long bLocalID); var result = gotA.CompareTo(gotB); if (result != 0) return result; result = aGUID.CompareTo(bGUID); if (result != 0) return result; return aLocalID.CompareTo(bLocalID); } /// Compares the asset names. private static int CompareCachedNames(TransitionAssetBase a, TransitionAssetBase b) => a.GetCachedName().CompareTo(b.GetCachedName()); /// Compares the asset paths. private static int ComparePaths(TransitionAssetBase a, TransitionAssetBase b) => AssetDatabase.GetAssetPath(a).CompareTo(AssetDatabase.GetAssetPath(b)); /// Compares the transition types. private static int CompareTypes(TransitionAssetBase a, TransitionAssetBase b) { if (AnimancerUtilities.TryGetWrappedObject(a, out var transitionA) && AnimancerUtilities.TryGetWrappedObject(b, out var transitionB)) { var result = transitionA.GetType().GetNameCS().CompareTo(transitionB.GetType().GetNameCS()); if (result != 0) return result; } return a.GetType().GetNameCS().CompareTo(b.GetType().GetNameCS()); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Sorting /************************************************************************************************************************/ private static TransitionAssetBase[] _SortingTransitions = Array.Empty(); private static int[] _OldIndexToNew; /************************************************************************************************************************/ /// Sorts the . public static void Sort( TransitionLibraryDefinition library, Comparison comparison) => Sort(library, new Comparison(comparison)); /// Sorts the . public static void Sort( TransitionLibraryDefinition library, IComparer comparer) { var transitions = library.Transitions; var count = transitions.Length; if (_SortingTransitions.Length < count) { var length = Mathf.NextPowerOfTwo(count); _SortingTransitions = new TransitionAssetBase[length]; _OldIndexToNew = new int[length]; } Array.Copy(transitions, _SortingTransitions, count); // Indices 0 -> Count. var newIndexToOld = GetTempSequentialIndices(count); Array.Sort(_SortingTransitions, newIndexToOld, 0, count, comparer); // Remove nulls which should have been sorted to the end. for (int i = count - 1; i >= 0; i--) if (_SortingTransitions[i] == null) count--; else break; // _NewIndexToOld[x] is now the index that Transitions[x] was at previously. // We need to invert that so _OldIndexToNew[x] is the new index of whatever was previously at Transitions[x]. // That allows the library to update any index references using a simple x = _OldIndexToNew[x]; for (int i = 0; i < count; i++) _OldIndexToNew[newIndexToOld[i]] = i; SetTransitions(library, _SortingTransitions, _OldIndexToNew, count); } /************************************************************************************************************************/ /// /// Sets the /// using `oldIndexToNew` to remap any references to the old order. /// public static void SetTransitions( TransitionLibraryDefinition library, TransitionAssetBase[] transitions, int[] oldIndexToNew, int count) { var libraryTransitions = library.Transitions; if (libraryTransitions != transitions) { AnimancerUtilities.SetLength(ref libraryTransitions, count); Array.Copy(transitions, libraryTransitions, count); library.Transitions = libraryTransitions; } var modifiers = library.Modifiers; for (int i = modifiers.Length - 1; i >= 0; i--) { var modifier = modifiers[i]; var isValid = true; var fromIndex = ConvertIndex(modifier.FromIndex, oldIndexToNew, count, ref isValid); var toIndex = ConvertIndex(modifier.ToIndex, oldIndexToNew, count, ref isValid); if (isValid) modifiers[i] = modifier.WithIndices(fromIndex, toIndex); else AnimancerUtilities.RemoveAt(ref modifiers, i); } var aliases = library.Aliases; for (int i = aliases.Length - 1; i >= 0; i--) { var alias = aliases[i]; var isValid = true; var index = ConvertIndex(alias.Index, oldIndexToNew, count, ref isValid); if (isValid) aliases[i] = alias.With(index); else AnimancerUtilities.RemoveAt(ref aliases, i); } library.SortAliases(); } /************************************************************************************************************************/ /// Converts an old index to a new one. private static int ConvertIndex(int index, int[] oldIndexToNew, int count, ref bool isValid) { if ((uint)index >= (uint)count) { isValid = false; return -1; } index = oldIndexToNew[index]; if ((uint)index >= (uint)count) { isValid = false; return -1; } return index; } /************************************************************************************************************************/ private static int[] _SequentialIndices = Array.Empty(); /// Returns a cached array containing sequential indices, i.e. array[i] = i. public static int[] GetTempSequentialIndices(int count) { if (_SequentialIndices.Length < count) _SequentialIndices = new int[Mathf.NextPowerOfTwo(count)]; for (int i = 0; i < _SequentialIndices.Length; i++) _SequentialIndices[i] = i; return _SequentialIndices; } /************************************************************************************************************************/ /// Changes the index of a transition. public static void MoveTransition(TransitionLibraryWindow window, int from, int to) { var transitions = window.Data.Transitions; to = Mathf.Clamp(to, 0, transitions.Length - 1); if (from == to) return; var editorData = window.SourceObject.GetOrCreateEditorData(); var definition = window.RecordUndo(); editorData.TransitionSortMode = TransitionSortMode.Custom; var moving = transitions[from]; var indices = GetTempSequentialIndices(transitions.Length); if (to > from)// Moving forwards. { Array.Copy(transitions, from + 1, transitions, from, to - from); Array.Copy(indices, from, indices, from + 1, to - from); } else// Moving backwards. { Array.Copy(transitions, to, transitions, to + 1, from - to); Array.Copy(indices, to + 1, indices, to, from - to); } transitions[to] = moving; indices[from] = to; SetTransitions( definition, transitions, indices, transitions.Length); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } } #endif