From ac2e5f2db5a4caffb76011004c7c0a535e2babd8 Mon Sep 17 00:00:00 2001
From: sator-imaging <16752340+sator-imaging@users.noreply.github.com>
Date: Mon, 12 Aug 2024 11:52:53 +0900
Subject: [PATCH] feat: Nullable support for UnityEngine.Object

---
 Editor.meta                         |   8 ++
 Runtime.meta                        |   8 ++
 Runtime/NullableUnityObject.cs      | 118 ++++++++++++++++++++++++++++
 Runtime/NullableUnityObject.cs.meta |  11 +++
 4 files changed, 145 insertions(+)
 create mode 100644 Editor.meta
 create mode 100644 Runtime.meta
 create mode 100644 Runtime/NullableUnityObject.cs
 create mode 100644 Runtime/NullableUnityObject.cs.meta

diff --git a/Editor.meta b/Editor.meta
new file mode 100644
index 0000000..8a4aafe
--- /dev/null
+++ b/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 28b70498b04f473479e562cd3435c838
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Runtime.meta b/Runtime.meta
new file mode 100644
index 0000000..7b36a67
--- /dev/null
+++ b/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 830b3c630a7da964487db94e033c6ad4
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Runtime/NullableUnityObject.cs b/Runtime/NullableUnityObject.cs
new file mode 100644
index 0000000..7514aeb
--- /dev/null
+++ b/Runtime/NullableUnityObject.cs
@@ -0,0 +1,118 @@
+/** Nullable support for `UnityEngine.Object`
+ ** (c) 2024 https://github.com/sator-imaging
+ ** Licensed under the MIT License
+
+How to Use
+==========
+Use `.Nullable()` extension method when using null-coalescing operator with `UnityEngine.Object` which may be null.
+
+```cs
+var rb = go.GetComponent<Rigidbody>().Nullable() ?? go.AddComponent<Rigidbody>();
+var rb = go.GetComponent<Rigidbody>().Nullable() ?? throw new Exception();
+```
+
+ */
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Object = UnityEngine.Object;
+
+#nullable enable
+
+namespace SatorImaging.UnityFundamentals
+{
+
+#pragma warning disable CS0618  // NOTE: to prevent misuse, NullableUnityObject<T> is marked as obsolete
+
+    public static class NullableUnityObjectExtensions
+    {
+        // NOTE: don't use [DoesNotReturn] attribute!!
+        //       it will completely stop nullability analysis in context after method call!!
+
+        // TODO: not able to specify method return value by [NotNullIfNotNull("return")]
+        //       any way to mark `self` is not null after this method is called?
+        // NOTE: if first parameter is `this T self`, it shows warning when `self` type is `GameObject?` or other nullable ref type.
+        //       [AllowNull] won't work as expected in this case. to suppress warning, it must be declared as `this T? self`.
+        // NOTE: no performance gain so should not set inlining explicitly
+        //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static NullableUnityObject<T>? Nullable<T>([AllowNull] this T? self)  // must be `this T? self`
+            where T : Object
+        {
+            return (self == null) ? null : new NullableUnityObject<T>(self);
+        }
+
+
+        /// <exception cref="NullReferenceException"></exception>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        [return: NotNull]
+        [Obsolete("use `.Nullable() ?? throw new...` instead")]
+        public static T ThrowIfNull<T>([AllowNull] this T? self, string? message = null, Exception? innerException = null)  // must be `this T? self`
+            where T : Object
+        {
+            if (self == null)
+                throw new NullReferenceException(message, innerException);
+
+            return self;
+        }
+    }
+#pragma warning restore CS0618
+
+
+    /*  typedef  ================================================================ */
+
+    /// <summary>
+    /// > [!CAUTION]
+    /// > !!! this struct must not be used except for designated usage !!!
+    /// </summary>
+    [Obsolete("!!! this struct must not be used except for designated usage !!!")]
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public readonly struct NullableUnityObject<T> : IEquatable<NullableUnityObject<T>>, IEquatable<T>
+        where T : Object
+    {
+        readonly T value;
+
+        [Obsolete("use `Nullable()` extension method for unity object instead of instantiating manually")]
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal NullableUnityObject(T value)
+        {
+            //if (value == null)
+            //    throw new ArgumentNullException(nameof(value));
+
+            this.value = value;
+        }
+
+        readonly public T Value
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get => value;
+        }
+
+        readonly public bool HasValue
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get => value != null;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static implicit operator T(NullableUnityObject<T> self) => self.value;
+
+        readonly public override string ToString() => value.ToString();
+        readonly public override int GetHashCode() => value.GetHashCode();
+
+        // NOTE: UnityEngine.Object.Equals has NOT overriden!!
+        //       use == or != operator!!
+        readonly public override bool Equals(object? obj) => (obj is NullableUnityObject<T> nuo && nuo.value == value) || (obj is T uo && uo == value);
+        readonly public bool Equals(T? other) => value == other;
+        readonly public bool Equals(NullableUnityObject<T> other) => value == other.value;
+
+        public static bool operator ==(NullableUnityObject<T> left, NullableUnityObject<T> right) => left.value == right.value;
+        public static bool operator !=(NullableUnityObject<T> left, NullableUnityObject<T> right) => left.value != right.value;
+        public static bool operator ==(NullableUnityObject<T> left, T? right) => left.value == right;
+        public static bool operator !=(NullableUnityObject<T> left, T? right) => left.value != right;
+        public static bool operator ==(T? left, NullableUnityObject<T> right) => left == right.value;
+        public static bool operator !=(T? left, NullableUnityObject<T> right) => left != right.value;
+    }
+}
diff --git a/Runtime/NullableUnityObject.cs.meta b/Runtime/NullableUnityObject.cs.meta
new file mode 100644
index 0000000..602056c
--- /dev/null
+++ b/Runtime/NullableUnityObject.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 60ff2b47892d27b44b14eeca5fe82f52
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: