// 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