chore: initial commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e5936589d9791a4288596c4b383b592
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only] Caches <see cref="AnimationClip.events"/> to reduce garbage allocations.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimationEventCache
|
||||
///
|
||||
public static class AnimationEventCache
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly ConditionalWeakTable<AnimationClip, AnimationEvent[]>
|
||||
ClipToEvents = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="AnimationClip.events"/> and caches the result to avoid allocating more memory with
|
||||
/// each subsequent call.
|
||||
/// </summary>
|
||||
public static AnimationEvent[] GetCachedEvents(this AnimationClip clip)
|
||||
{
|
||||
if (!ClipToEvents.TryGetValue(clip, out var events))
|
||||
{
|
||||
events = clip.events;
|
||||
ClipToEvents.Add(clip, events);
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clears the cache.</summary>
|
||||
public static void Clear()
|
||||
=> ClipToEvents.Clear();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Removes the `clip` from the cache so its events will be retrieved again next time.</summary>
|
||||
public static void Remove(AnimationClip clip)
|
||||
=> ClipToEvents.Remove(clip);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb828e75ad28a6441b43f561f568f98d
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,140 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only] A cache to optimize repeated attribute access.</summary>
|
||||
/// <remarks>
|
||||
/// If <typeparamref name="TAttribute"/> implements <see cref="IInitializable{T}"/> for <see cref="MemberInfo"/>,
|
||||
/// its <see cref="IInitializable{T}.Initialize(T)"/> method will be called automatically.
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AttributeCache_1
|
||||
public static class AttributeCache<TAttribute>
|
||||
where TAttribute : class
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Dictionary<MemberInfo, TAttribute>
|
||||
MemberToAttribute = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <typeparamref name="TAttribute"/> attribute on the specified `member` (if there is one).
|
||||
/// </summary>
|
||||
public static TAttribute GetAttribute(MemberInfo member)
|
||||
{
|
||||
if (!MemberToAttribute.TryGetValue(member, out var attribute))
|
||||
{
|
||||
try
|
||||
{
|
||||
attribute = member.GetAttribute<TAttribute>();
|
||||
|
||||
if (attribute is IInitializable<MemberInfo> initializable)
|
||||
initializable.Initialize(member);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
attribute = null;
|
||||
}
|
||||
|
||||
MemberToAttribute.Add(member, attribute);
|
||||
}
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <typeparamref name="TAttribute"/> attribute (if any)
|
||||
/// on the specified `type` or its <see cref="Type.BaseType"/> (recursively).
|
||||
/// </summary>
|
||||
public static TAttribute GetAttribute(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
var attribute = GetAttribute((MemberInfo)type);
|
||||
if (attribute != null)
|
||||
return attribute;
|
||||
|
||||
return MemberToAttribute[type] = GetAttribute(type.BaseType);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <typeparamref name="TAttribute"/> attribute on the specified `field` or its
|
||||
/// <see cref="FieldInfo.FieldType"/> or <see cref="MemberInfo.DeclaringType"/>.
|
||||
/// </summary>
|
||||
public static TAttribute FindAttribute(FieldInfo field)
|
||||
{
|
||||
var attribute = GetAttribute(field);
|
||||
if (attribute != null)
|
||||
return attribute;
|
||||
|
||||
attribute = GetAttribute(field.FieldType);
|
||||
if (attribute != null)
|
||||
return MemberToAttribute[field] = attribute;
|
||||
|
||||
attribute = GetAttribute(field.DeclaringType);
|
||||
if (attribute != null)
|
||||
return MemberToAttribute[field] = attribute;
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// Returns the <typeparamref name="TAttribute"/> attribute on the underlying field
|
||||
/// of the `property` or its <see cref="FieldInfo.FieldType"/> or
|
||||
/// <see cref="MemberInfo.DeclaringType"/> or any of the parent properties
|
||||
/// or the type of the <see cref="SerializedObject.targetObject"/>.
|
||||
/// </summary>
|
||||
public static TAttribute FindAttribute(SerializedProperty property)
|
||||
{
|
||||
var accessor = property.GetAccessor();
|
||||
while (accessor != null)
|
||||
{
|
||||
var field = accessor.GetField(property);
|
||||
var attribute = GetAttribute(field);
|
||||
if (attribute != null)
|
||||
return attribute;
|
||||
|
||||
var value = accessor.GetValue(property);
|
||||
if (value != null)
|
||||
{
|
||||
attribute = GetAttribute(value.GetType());
|
||||
if (attribute != null)
|
||||
return attribute;
|
||||
}
|
||||
|
||||
accessor = accessor.Parent;
|
||||
}
|
||||
|
||||
// If none of the fields of types they are declared in have names, try the actual type of the target.
|
||||
{
|
||||
var attribute = GetAttribute(property.serializedObject.targetObject.GetType());
|
||||
if (attribute != null)
|
||||
return attribute;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea23cad192fdb024b83eb42c328bc444
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,263 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
// Inspector Gadgets // https://kybernetik.com.au/animancer // Copyright 2017-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using Animancer.Editor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Animancer.Units.Editor
|
||||
//namespace InspectorGadgets.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A system for formatting floats as strings that fit into a limited area and storing the results so they can be
|
||||
/// reused to minimise the need for garbage collection, particularly for string construction.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// This system only affects the display value. Once you select a field, it shows its actual value.
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong>
|
||||
/// With <c>"x"</c> as the suffix:
|
||||
/// <list type="bullet">
|
||||
/// <item><c>1.111111</c> could instead show <c>1.111~x</c>.</item>
|
||||
/// <item><c>0.00001234567</c> would normally show <c>1.234567e-05</c>, but with this it instead shows <c>0~x</c>
|
||||
/// because very small values generally aren't useful.</item>
|
||||
/// <item><c>99999999</c> shows <c>1e+08x</c> because very large values are already approximations and trying to
|
||||
/// format them correctly would be very difficult.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
///
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units.Editor/CompactUnitConversionCache
|
||||
/// https://kybernetik.com.au/inspector-gadgets/api/InspectorGadgets.Editor/CompactUnitConversionCache
|
||||
///
|
||||
public class CompactUnitConversionCache
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Should the fields show approximations if the value is too long for the GUI?</summary>
|
||||
public static bool ShowApproximations
|
||||
=> AnimancerSettingsGroup<AnimationTimeAttributeSettings>.Instance.showApproximations;
|
||||
// => PropertyDrawers.TransformPropertyDrawer.ShowApproximations;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The suffix added to the end of each value.</summary>
|
||||
public string Suffix;
|
||||
|
||||
/// <summary>The <see cref="Suffix"/> with a <c>~</c> before it to indicate an approximation.</summary>
|
||||
public string ApproximateSuffix;
|
||||
|
||||
/// <summary>The value <c>0</c> with the <see cref="Suffix"/>.</summary>
|
||||
public string ConvertedZero;
|
||||
|
||||
/// <summary>The value <c>0</c> with the <see cref="ApproximateSuffix"/>.</summary>
|
||||
public string ConvertedSmallPositive;
|
||||
|
||||
/// <summary>The value <c>-0</c> with the <see cref="ApproximateSuffix"/>.</summary>
|
||||
public string ConvertedSmallNegative;
|
||||
|
||||
/// <summary>The string to return when converting <see cref="float.NaN"/>.</summary>
|
||||
public string ConvertedNaN;
|
||||
|
||||
/// <summary>The pixel width of the <see cref="Suffix"/> when drawn by <see cref="EditorStyles.numberField"/>.</summary>
|
||||
public float _SuffixWidth;
|
||||
|
||||
/// <summary>The caches for each character count.</summary>
|
||||
/// <remarks><c>this[x]</c> is a cache that outputs strings with <c>x</c> characters.</remarks>
|
||||
private readonly List<ConversionCache<float, string>>
|
||||
Caches = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Strings mapped to the width they would require for a <see cref="EditorStyles.numberField"/>.</summary>
|
||||
private static ConversionCache<string, float> _WidthCache;
|
||||
|
||||
/// <summary>Padding around the text in a <see cref="EditorStyles.numberField"/>.</summary>
|
||||
public static float _FieldPadding;
|
||||
|
||||
/// <summary>The pixel width of the <c>~</c> character when drawn by <see cref="EditorStyles.numberField"/>.</summary>
|
||||
public static float _ApproximateSymbolWidth;
|
||||
|
||||
/// <summary>The character(s) used to separate decimal values in the current OS language.</summary>
|
||||
public static string _DecimalSeparator;
|
||||
|
||||
/// <summary>Values smaller than this become <c>0~</c> or <c>-0~</c>.</summary>
|
||||
public const float
|
||||
SmallExponentialThreshold = 0.0001f;
|
||||
|
||||
/// <summary>Values larger than this can't be approximated.</summary>
|
||||
public const float
|
||||
LargeExponentialThreshold = 9999999f;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="CompactUnitConversionCache"/>.</summary>
|
||||
public CompactUnitConversionCache(string suffix)
|
||||
{
|
||||
Suffix = suffix;
|
||||
ApproximateSuffix = "~" + Suffix;
|
||||
ConvertedZero = "0" + Suffix;
|
||||
ConvertedNaN = "NaN" + Suffix;
|
||||
ConvertedSmallPositive = "0" + ApproximateSuffix;
|
||||
ConvertedSmallNegative = "-0" + ApproximateSuffix;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns a cached string representing the `value` trimmed to fit within the `width` (if necessary)
|
||||
/// and with the <see cref="Suffix"/> added on the end.
|
||||
/// </summary>
|
||||
public string Convert(float value, float width)
|
||||
{
|
||||
if (value == 0)
|
||||
return ConvertedZero;
|
||||
else if (float.IsNaN(value))
|
||||
return ConvertedNaN;
|
||||
|
||||
if (!ShowApproximations)
|
||||
return GetCache(0).Convert(value);
|
||||
|
||||
if (value < SmallExponentialThreshold &&
|
||||
value > -SmallExponentialThreshold)
|
||||
return value > 0 ? ConvertedSmallPositive : ConvertedSmallNegative;
|
||||
|
||||
var index = CalculateCacheIndex(value, width);
|
||||
return GetCache(index).Convert(value);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculate the index of the cache to use for the given parameters.</summary>
|
||||
private int CalculateCacheIndex(float value, float width)
|
||||
{
|
||||
//if (value > LargeExponentialThreshold ||
|
||||
// value < -LargeExponentialThreshold)
|
||||
// return 0;
|
||||
|
||||
var valueString = value.ToStringCached();
|
||||
|
||||
// It the approximated string wouldn't be shorter than the original, don't approximate.
|
||||
if (valueString.Length < 2 + ApproximateSuffix.Length)
|
||||
return 0;
|
||||
|
||||
if (_SuffixWidth == 0)
|
||||
{
|
||||
if (_WidthCache == null)
|
||||
{
|
||||
_WidthCache = ConversionCache.CreateWidthCache(EditorStyles.numberField);
|
||||
_FieldPadding = EditorStyles.numberField.padding.horizontal;
|
||||
_ApproximateSymbolWidth = _WidthCache.Convert("~") - _FieldPadding;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Suffix))
|
||||
_SuffixWidth = _WidthCache.Convert(Suffix);
|
||||
}
|
||||
|
||||
// If the field is wide enough to fit the full value, don't approximate.
|
||||
width -= _FieldPadding + _ApproximateSymbolWidth * 0.75f;
|
||||
var valueWidth = _WidthCache.Convert(valueString) + _SuffixWidth;
|
||||
if (valueWidth <= width)
|
||||
return 0;
|
||||
|
||||
// If the number of allowed characters would include the full value, don't approximate.
|
||||
var suffixedLength = valueString.Length + Suffix.Length;
|
||||
var allowedCharacters = (int)(suffixedLength * width / valueWidth);
|
||||
if (allowedCharacters + 2 >= suffixedLength)
|
||||
return 0;
|
||||
|
||||
return allowedCharacters;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates and returns a cache for the specified `characterCount`.</summary>
|
||||
private ConversionCache<float, string> GetCache(int characterCount)
|
||||
{
|
||||
while (Caches.Count <= characterCount)
|
||||
Caches.Add(null);
|
||||
|
||||
var cache = Caches[characterCount];
|
||||
if (cache == null)
|
||||
{
|
||||
if (characterCount == 0)
|
||||
{
|
||||
cache = new((value) =>
|
||||
{
|
||||
return value.ToStringCached() + Suffix;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
cache = new((value) =>
|
||||
{
|
||||
var valueString = value.ToStringCached();
|
||||
|
||||
if (value > LargeExponentialThreshold ||
|
||||
value < -LargeExponentialThreshold)
|
||||
goto IsExponential;
|
||||
|
||||
_DecimalSeparator ??= CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
|
||||
|
||||
var decimalIndex = valueString.IndexOf(_DecimalSeparator);
|
||||
if (decimalIndex < 0 || decimalIndex > characterCount)
|
||||
goto IsExponential;
|
||||
|
||||
// Not exponential.
|
||||
return valueString[..characterCount] + ApproximateSuffix;
|
||||
|
||||
IsExponential:
|
||||
var digits = Math.Max(0, characterCount - ApproximateSuffix.Length - 1);
|
||||
var format = GetExponentialFormat(digits);
|
||||
valueString = value.ToString(format);
|
||||
TrimExponential(ref valueString);
|
||||
return valueString + Suffix;
|
||||
});
|
||||
}
|
||||
|
||||
Caches[characterCount] = cache;
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static List<string> _ExponentialFormats;
|
||||
|
||||
/// <summary>Returns a format string to include the specified number of `digits` in an exponential number.</summary>
|
||||
public static string GetExponentialFormat(int digits)
|
||||
{
|
||||
_ExponentialFormats ??= new();
|
||||
|
||||
while (_ExponentialFormats.Count <= digits)
|
||||
_ExponentialFormats.Add("g" + _ExponentialFormats.Count);
|
||||
|
||||
return _ExponentialFormats[digits];
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void TrimExponential(ref string valueString)
|
||||
{
|
||||
var length = valueString.Length;
|
||||
if (length <= 4 ||
|
||||
valueString[length - 4] != 'e' ||
|
||||
valueString[length - 2] != '0')
|
||||
return;
|
||||
|
||||
valueString =
|
||||
valueString[..(length - 2)] +
|
||||
valueString[length - 1];
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b282ce649e44335499d21e0472c670e1
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
// Inspector Gadgets // https://kybernetik.com.au/inspector-gadgets // Copyright 2017-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
//#define LOG_CONVERSION_CACHE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// Shared File Last Modified: 2023-09-02
|
||||
namespace Animancer.Editor
|
||||
//namespace InspectorGadgets.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A simple system for converting objects and storing the results so they can be reused to minimise the need for
|
||||
/// garbage collection, particularly for string construction.
|
||||
/// </summary>
|
||||
/// <remarks>This class doesn't use any Editor-Only functionality, but it's unlikely to be useful at runtime.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/ConversionCache_2
|
||||
/// https://kybernetik.com.au/inspector-gadgets/api/InspectorGadgets.Editor/ConversionCache_2
|
||||
///
|
||||
public class ConversionCache<TKey, TValue>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private class CachedValue
|
||||
{
|
||||
public int lastFrameAccessed;
|
||||
public TValue value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private readonly Dictionary<TKey, CachedValue>
|
||||
Cache = new();
|
||||
private readonly List<TKey>
|
||||
Keys = new();
|
||||
private readonly Func<TKey, TValue>
|
||||
Converter;
|
||||
|
||||
private int _LastCleanupFrame;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ConversionCache{TKey, TValue}"/> which uses the specified delegate to convert values.
|
||||
/// </summary>
|
||||
public ConversionCache(Func<TKey, TValue> converter)
|
||||
=> Converter = converter;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If a value has already been cached for the specified `key`, return it. Otherwise create a new one using
|
||||
/// the delegate provided in the constructor and cache it.
|
||||
/// <para></para>
|
||||
/// If the `key` is <c>null</c>, this method returns the default <typeparamref name="TValue"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This method also periodically removes values that have not been used recently.</remarks>
|
||||
public TValue Convert(TKey key)
|
||||
{
|
||||
if (key == null)
|
||||
return default;
|
||||
|
||||
CachedValue cached;
|
||||
|
||||
// The next time a value is retrieved after at least 100 frames, clear out any old ones.
|
||||
var frame = Time.frameCount;
|
||||
if (_LastCleanupFrame + 100 < frame)
|
||||
{
|
||||
|
||||
for (int i = Keys.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var checkKey = Keys[i];
|
||||
if (!Cache.TryGetValue(checkKey, out cached) ||
|
||||
cached.lastFrameAccessed <= _LastCleanupFrame)
|
||||
{
|
||||
Cache.Remove(checkKey);
|
||||
Keys.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
_LastCleanupFrame = frame;
|
||||
|
||||
}
|
||||
|
||||
if (!Cache.TryGetValue(key, out cached))
|
||||
{
|
||||
Cache.Add(key, cached = new() { value = Converter(key) });
|
||||
Keys.Add(key);
|
||||
|
||||
}
|
||||
|
||||
cached.lastFrameAccessed = frame;
|
||||
|
||||
return cached.value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] Utilities for <see cref="ConversionCache{TKey, TValue}"/>.</summary>
|
||||
/// <remarks>This class doesn't use any Editor-Only functionality, but it's unlikely to be useful at runtime.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/ConversionCache
|
||||
/// https://kybernetik.com.au/inspector-gadgets/api/InspectorGadgets.Editor/ConversionCache
|
||||
///
|
||||
public static class ConversionCache
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ConversionCache{TKey, TValue}"/> for calculating the GUI width occupied by text using
|
||||
/// the specified `style`.
|
||||
/// </summary>
|
||||
public static ConversionCache<string, float> CreateWidthCache(GUIStyle style)
|
||||
=> new(style.CalculateWidth);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
// The "g" format gives a lower case 'e' for exponentials instead of upper case 'E'.
|
||||
private static readonly ConversionCache<float, string>
|
||||
FloatToString = new((value) => $"{value:g}");
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Calls <see cref="float.ToString(string)"/> using <c>"g"</c> as the format and caches the result.
|
||||
/// </summary>
|
||||
public static string ToStringCached(this float value)
|
||||
=> FloatToString.Convert(value);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b0e970556a97204991d267c5cdb3ca9
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user