Skip to content

Instantly share code, notes, and snippets.

@yokljo

yokljo/Option.cs Secret

Last active May 31, 2020 00:01
Show Gist options
  • Save yokljo/55d27e0f6884ce61fb001952609cc65e to your computer and use it in GitHub Desktop.
Save yokljo/55d27e0f6884ce61fb001952609cc65e to your computer and use it in GitHub Desktop.
Option<T> works like Nullable<T>, but is compatible with Unity 2020's serialisation system.
// Copyright © 2020 Joshua Worth.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>An interface that all <c>Option<T></c> types implement.</summary>
public interface OptionInterface {
/// <summary>Get the <c>Option<T></c> as a possibly-null object.
/// Warning: This will box the struct value, causing an allocation.</summary>
object GetObjectValue();
}
/// <summary>For Unity version 2020.*. An optional value, compatible with Unity's serialisation
/// system. This can be used in place of the <c>System.Nullable<T></c> type.</summary>
[System.Serializable]
public struct Option<T> : OptionInterface where T: struct {
[SerializeField] private bool hasValue;
/// <summary>True when the Option has a Value. Check this before accessing Value.</summary>
public bool HasValue => hasValue;
[SerializeField] private T value;
/// <summary>Get the value of this Option. Check HasValue before accessing Value.</summary>
public T Value => value;
/// <summary>Convert this <c>Option<T></c> to an instance of Nullable<T>.</summary>
public T? Nullable => hasValue ? (T?)value : null;
/// <summary>Make an Option instance where HasValue is true and Value is set to value.</summary>
public Option(T value) {
hasValue = true;
this.value = value;
}
/// <summary>Convert a Nullable<T> to an <c>Option<T></c>.</summary>
public Option(T? value) {
hasValue = value.HasValue;
if (hasValue) {
this.value = value.Value;
} else {
this.value = default(T);
}
}
/// <summary>Get Value if HasValue is true, otherwise return defaultValue.</summary>
public T GetValueOrDefault(T defaultValue) => hasValue ? value : defaultValue;
/// <summary>Get the <c>Option<T></c> as a possibly-null object.
/// Warning: This will box the struct value, causing an allocation.</summary>
public object GetObjectValue() {
return hasValue ? (object)value : (object)null;
}
/// <summary>Returns the string of the Value if HasValue is true, otherwise it returns "null".</summary>
public override string ToString() {
return hasValue ? value.ToString() : "null";
}
/// <summary>Returns the hash code of the Value if HasValue is true, otherwise it returns zero.</summary>
public override int GetHashCode() {
return hasValue ? value.GetHashCode() : 0;
}
/// <summary>Convert a <c>Nullable<T></c> to an <c>Option<T></c>.
public static implicit operator Option<T>(T? value) {
return new Option<T>(value);
}
/// <summary>Convert an <c>Option<T></c> to a <c>Nullable<T></c>.
public static implicit operator T?(Option<T> value) {
return value.Nullable;
}
/// <summary>Convert a <c>T</c> to an <c>Option<T></c> where HasValue is true and Value is set to value.</summary>
public static implicit operator Option<T>(T value) {
return new Option<T>(value);
}
/// <summary>An Option acts as a true value when HasValue is true.</summary>
public static implicit operator bool(Option<T> option) {
return option.hasValue;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(Option<>))]
class OptionDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
using (var optionScope = new EditorGUI.PropertyScope(position, GUIContent.none, property)) {
var toggleRect = position;
toggleRect.width = EditorGUIUtility.labelWidth + EditorGUIUtility.singleLineHeight;
var valueRect = position;
valueRect.xMin += toggleRect.width;
var hasValueProp = property.FindPropertyRelative("hasValue");
hasValueProp.boolValue = EditorGUI.Toggle(toggleRect, property.displayName + "?", hasValueProp.boolValue);
if (hasValueProp.boolValue) {
EditorGUI.PropertyField(valueRect, property.FindPropertyRelative("value"), GUIContent.none);
} else {
EditorGUI.LabelField(valueRect, "No value");
}
}
}
}
#endif
// Replace the inspector with this one if you like. I personally prefer it.
// Copyright © 2020 Joshua Worth.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(Option<>))]
class OptionDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
using (var optionScope = new EditorGUI.PropertyScope(position, GUIContent.none, property)) {
var toggleRect = position;
toggleRect.width = EditorGUIUtility.labelWidth;
var valueRect = position;
valueRect.xMin += toggleRect.width;
var hasValueProp = property.FindPropertyRelative("hasValue");
hasValueProp.boolValue = EditorGUI.ToggleLeft(toggleRect, property.displayName + "?", hasValueProp.boolValue);
if (hasValueProp.boolValue) {
EditorGUI.PropertyField(valueRect, property.FindPropertyRelative("value"), GUIContent.none);
} else {
EditorGUI.LabelField(valueRect, "---");
}
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment