Code/General/SingletonComponent.cs
using Sandbox;
using System.Runtime.CompilerServices;
using System.Threading;

namespace CubeCore.Core.SandboxExtensions;

public interface ISingleton<T> where T : ISingleton<T>
{
    static abstract T? Instance { get; }
}

public abstract class SingletonComponent<T> : Component, ISingleton<T> where T : SingletonComponent<T>, ISingleton<T>
{
    [SkipHotload]
    public static T? Instance
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get
        {
            var currentValue = field;
            if(currentValue.IsValid())
                return currentValue;

            field = null;
            var scene = Game.ActiveScene;
            if(scene is null || scene.IsEditor)
                return null;

            var found = scene?.Get<T>();
            var oldValue = Interlocked.CompareExchange(ref field, found, currentValue);
            if(ReferenceEquals(oldValue, currentValue))
                return found.IsValid() ? found : null;

            currentValue = field;
            return currentValue.IsValid() ? currentValue : null;
        }
        private set
        {
            Interlocked.Exchange(ref field, value);
        }
    }

    protected sealed override void OnAwake()
    {
        if(!Scene.IsEditor)
        {
            if(Instance.IsValid() && Instance != this)
            {
                Log.Warning($"Enabled {this} (on {GameObject}), but another instance {Instance} was already enabled. Disabling {this}...");
                return;
            }

            GameObject.Flags |= GameObjectFlags.DontDestroyOnLoad;
            Instance = (T?)this;
        }

        OnAwake(false);
    }
    protected virtual void OnAwake(bool _) { }

    protected sealed override void OnDestroy()
    {
        OnDestroy(false);

        if(!Scene.IsEditor && object.ReferenceEquals(Instance, this))
            Instance = null;
    }
    protected virtual void OnDestroy(bool _) { }
}