Skip to content

Instantly share code, notes, and snippets.

@SolidAlloy
Created November 19, 2024 12:32
Show Gist options
  • Save SolidAlloy/9204abb5921a8e1dffc8739d46451d87 to your computer and use it in GitHub Desktop.
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
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