Created
November 19, 2024 12:32
-
-
Save SolidAlloy/9204abb5921a8e1dffc8739d46451d87 to your computer and use it in GitHub Desktop.
A serializable enum that can recover its state when the underlying int value changes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using Sirenix.OdinInspector; | |
using UnityEngine; | |
using UnityEngine.Assertions; | |
using Debug = UnityEngine.Debug; | |
namespace SolidAlloy | |
{ | |
// A helper that allows changing the order of elements in an enum without breaking serialization. | |
[Serializable, InlineProperty] | |
public struct SerializedEnum<T> : ISerializationCallbackReceiver, IEquatable<SerializedEnum<T>> where T : unmanaged, Enum | |
{ | |
[SerializeField, HideInInspector] string valueName; | |
[SerializeField, HideInInspector] int valueIndex; | |
[NonSerialized, ShowInInspector, HideLabel] public T Value; | |
public SerializedEnum(T value) | |
{ | |
valueName = default; | |
valueIndex = default; | |
Value = value; | |
} | |
void ISerializationCallbackReceiver.OnBeforeSerialize() | |
{ | |
AssertUnderlyingInt(); | |
// Since GetName boxes the enum value anyway, we use a simple approach to cast it to int. | |
// We don't really need performance here because OnBeforeSerialize is used only in editor. | |
object valueObj = Value; | |
valueName = Enum.GetName(typeof(T), valueObj); | |
valueIndex = (int) valueObj; | |
} | |
void ISerializationCallbackReceiver.OnAfterDeserialize() | |
{ | |
Value = GetValue(valueName, valueIndex); | |
} | |
public static T GetValue(string valueName, int valueIndex) | |
{ | |
if (Enum.TryParse(valueName, out T value)) | |
return value; | |
AssertUnderlyingInt(); | |
// Using unsafe approach to avoid boxing because OnAfterDeserialize is called in builds too. | |
unsafe | |
{ | |
return *(T*)&valueIndex; | |
} | |
} | |
public static implicit operator T(SerializedEnum<T> serEnum) | |
{ | |
return serEnum.Value; | |
} | |
public static explicit operator int(SerializedEnum<T> serEnum) | |
{ | |
AssertUnderlyingInt(); | |
unsafe | |
{ | |
return *(int*)&serEnum.Value; | |
} | |
} | |
public static implicit operator SerializedEnum<T>(T enumValue) => new(enumValue); | |
public override string ToString() => Value.ToString(); | |
[Conditional("DEBUG")] | |
public static void AssertUnderlyingInt() | |
{ | |
Assert.IsTrue(Enum.GetUnderlyingType(typeof(T)) == typeof(int), "Working with underlying types other than int is not implemented at the moment."); | |
} | |
#region Equality | |
public bool Equals(SerializedEnum<T> other) | |
{ | |
return EqualityComparer<T>.Default.Equals(Value, other.Value); | |
} | |
public override bool Equals(object obj) | |
{ | |
return obj is SerializedEnum<T> other && Equals(other); | |
} | |
public override int GetHashCode() | |
{ | |
return Value.GetHashCode(); | |
} | |
public static bool operator ==(SerializedEnum<T> left, SerializedEnum<T> right) | |
{ | |
return left.Equals(right); | |
} | |
public static bool operator !=(SerializedEnum<T> left, SerializedEnum<T> right) | |
{ | |
return !left.Equals(right); | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment