Code/Patterns/Singleton/PanelSingleton.cs
using System.Collections.Generic;
using Sandbox;
using Sandbox.Diagnostics;
namespace WackyLib.Patterns;
/// <summary>
/// Represents a Singleton component. Which is a component that we can access through an instance, and only one can be allowed in a scene at once.
/// In-game, duplicate instances are automatically destroyed. In the editor (with ExecuteInEditor), multiple instances are permitted to coexist
/// for tooling purposes, but a warning is logged, as only one instance will survive when the game runs (the latest awakened component).
/// </summary>
public abstract class PanelSingleton<T> : PanelComponent, IHotloadManaged where T : PanelSingleton<T>
{
#nullable enable
#pragma warning disable SB3000
// ReSharper disable once MemberCanBePrivate.Global
public static T? Instance { get; private set; }
#pragma warning restore SB3000
private readonly Logger Log = new( "Singleton" );
protected override void OnAwake()
{
// We're running ExecuteInEditor, which means we should ignore instances.
if ( ExecutingInEditor() )
{
Log.Info( $"OnAwake called in editor with ExecuteInEditor, creating once." );
if ( Active )
{
if ( Instance.IsValid() && Instance != this )
{
Log.Warning( $"Multiple {typeof(T)} instances detected in the scene! Only one will be used in-game." );
}
Instance = (T)this;
}
return;
}
if ( Instance.IsValid() )
{
Log.Warning( $"Singleton tried to initialize another {typeof(T)}!" );
Destroy();
return;
}
if ( Active )
{
Instance = (T)this;
}
}
protected override void OnDestroy()
{
if ( Instance == this )
{
Instance = null;
}
}
void IHotloadManaged.Destroyed( Dictionary<string, object> state )
{
state["IsActive"] = Instance == this;
}
void IHotloadManaged.Created( IReadOnlyDictionary<string, object> state )
{
if ( state.GetValueOrDefault( "IsActive" ) is true )
{
Instance = (T)this;
}
}
private bool ExecutingInEditor()
{
if ( !Game.IsEditor )
{
return false;
}
var type = GetType();
return typeof(ExecuteInEditor).IsAssignableFrom( type );
}
}