npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

com.gatongone.singleton

v1.0.0

Published

Build your singleton without base type.

Downloads

508

Readme

Singleton

Build a singleton in Unity without a base type.

using Singleton.Runtime;
using UnityEngine;

[Singleton]
public partial class GameManager : MonoBehaviour
{
    public int Score;
}

GameManager.Instance is the instance, and the same one every time it is read.

The type is declared partial, and nothing else is asked of it. No base type carries the instance, so a MonoBehaviour singleton is still a MonoBehaviour and a ScriptableObject singleton is still a ScriptableObject.

GameManager.TryGetInstance(out var manager) answers whether there is one already, and makes none where there is not: a message which must not bring a singleton into being asks this rather than Instance.

Manual

Attribute

With nothing, Unity makes the instance, or the type's own constructor does:

| The type | What Instance is | |----------|--------------------| | MonoBehaviour | one made on a new GameObject, or, with [Reusable], the loaded instance where there is one | | ScriptableObject | one made with CreateInstance | | anything else | new T(), so it needs a parameterless constructor of any accessibility |

[Singleton]
public partial class Foo { }

With a creator, ICreator<T> makes it:

public sealed class MyCreator : ICreator<MyObject>
{
    private readonly string m_Name;

    public MyCreator(string name) => m_Name = name;

    public MyObject Create() => new MyObject { Name = m_Name };
}

[Singleton(typeof(MyCreator), "MyName")]
public partial class MyObject
{
    public string Name;
}

The creator is made once, when the instance is first asked for, and is not held afterwards. The generator reads the arguments against the constructors of the creator, so a creator with no accessible constructor which they fit is reported as an error rather than left to the compiler.

With an asset type, the instance is loaded:

[Singleton(AssetType.Resources, "path/to/asset")]
public partial class ResourceMonoBehaviour : MonoBehaviour { }

[Singleton(AssetType.Resources, "path/to/asset")]
public partial class ResourceScriptableObject : ScriptableObject { }

[Singleton(AssetType.Addressable, "address/of/asset")]
public partial class AddressableMonoBehaviour : MonoBehaviour { }

[Singleton(AssetType.Addressable, "address/of/asset")]
public partial class AddressableScriptableObject : ScriptableObject { }

An asset type applies to a MonoBehaviour and to a ScriptableObject, and to nothing else. A MonoBehaviour which carries [Reusable] looks for an instance which is already loaded first, and only then loads the prefab and instantiates it; without that attribute it loads and instantiates straight away. A ScriptableObject loads the asset itself.

The path may be left out for AssetType.Resources, and the generator then reads it off the project: the first .prefab (for a MonoBehaviour) or .asset (for a ScriptableObject) which names the script which declares the type is the one which is loaded, and a project which holds none is an error. An Addressables address cannot be read off a project, so AssetType.Addressable asks for it.

Woven

A MonoBehaviour is given an Awake and an OnDestroy:

private void Awake()
{
    if (s_Instance != null && s_Instance != this)
    {
        Destroy(gameObject);
        return;
    }

    s_Instance = this;
    // ... the Awake which was written, if there was one
}

protected virtual void OnDestroy()
{
    if (s_Instance == this) s_Instance = null;
    // ... the OnDestroy which was written, if there was one
}

A ScriptableObject is given an OnEnable and an OnDisable instead, and a second one which is enabled is left out of the field rather than destroyed, because Unity loads a ScriptableObject only once.

Unity sends a message to the nearest declaration of it alone, and a declaration is what the weaver writes where the type wrote none, so the message a base class wrote is called from the one which was woven rather than left behind. A private message of a base class is given protected access to make that call, which is the only thing the weaving changes about the declaration a type wrote; a private message of an assembly which is not the one being compiled cannot be, and is left behind.

A singleton which derives from a singleton has an instance of its own, and Instance is declared new where the type it derives from declares one, because a member which is declared beside one of the same name of a type it derives from hides it. TryGetInstance is not: it answers with the type it is declared in, which no base type declares alike, so it is an overload rather than a member which hides one. A MonoBehaviour is looked for in the scene and a type which derives from another is one of it, so the two answer with the same object where there is one.

Extra

[Persistent, Invisible, Reusable]
public class Foo : MonoBehaviour
{
    // ...
}
  • [Persistent]: adds DontDestroyOnLoad(gameObject) to the Awake

  • [Invisible]: adds gameObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector | HideFlags.DontSave.

  • [Reusable]: the generated Instance looks for the object which is already in the scene before making one

[Persistent] and [Invisible] also stand on their own: a type which carries one of them and no [Singleton] is still given an Awake which does what the attribute asks. Where a type carries a singleton as well, the singleton is settled first and the attributes after it, so a second instance, which is given up, is neither kept nor hidden.

[Reusable] does not stand on its own: what it changes is the property which [Singleton] generates, so a type which carries it and asks for no singleton is one which nothing is generated for.

[Singleton, Reusable]
public partial class GameManager : MonoBehaviour
{
    public int Score;
}

GameManager.Instance is the GameManager which is already in the scene where there is one, and a new one where there is not. Without the attribute the property never looks: an object which was put in a scene by hand and which the woven Awake did not settle is one which Instance makes a second of.

The search is Object.FindAnyObjectByType<T>, which answers with the objects of a scene and with no asset of the project, so [Reusable] applies to a MonoBehaviour alone and is an error on anything else. A ScriptableObject is an asset, so the search would never answer with one, and a ScriptableObject singleton is always the one CreateInstance makes.

Two things the search leaves out are worth knowing:

  • an object which is not active is not found, so a singleton whose object is switched off is not the one Instance answers with;

  • an object which was given HideFlags.DontSave is not found, and those are the flags [Invisible] writes. A singleton which carries both is one whose own search never answers with it; the field still holds it, so what is given up is the search rather than the singleton.

Where CSHARP_11_OR_NEWER is defined, the generated half also declares the type as an ISingleton<T>, so that the same instance is read from a type argument alone:

T Read<T>() where T : class, ISingleton<T> => Singleton<T>.Instance;
bool TryRead<T>(out T instance) where T : class, ISingleton<T> => Singleton<T>.TryGetInstance(out instance);

Diagnostics

| ID | Descriptions | |----|---| | SING0001 | The type, or a type which holds it, is not declared partial. | | SING0002 | The type is generic, abstract, static, not a class, or declared inside a generic type. | | SING0003 | The type declares an Instance, an s_Instance or a TryGetInstance of its own. | | SING0004 | The type has no parameterless constructor for the generated Instance to call. | | SING0005 | The creator which was named does not implement ICreator<T> for the type. | | SING0006 | The creator has no accessible constructor which the named arguments fit. | | SING0007 | An asset type was asked for by a type which is neither a MonoBehaviour nor a ScriptableObject. | | SING0008 | No prefab or asset under a Resources folder holds the type. | | SING0009 | AssetType.Addressable was asked for without an address. | | SING0010 | The compilation does not see a type which the generated code would name, so the Addressables package is not referenced. | | SING0011 | PersistentAttribute was put on something which is not a MonoBehaviour. | | SING0012 | InvisibleAttribute was put on something which is not a MonoBehaviour. | | SING0013 | The Unity project could not be found, so a Resources path could not be read off it. | | SING0014 | ReusableAttribute was put on something which is not a MonoBehaviour, so no search for an object of a scene can be written. |

Principle

A singleton is written in two halves, because the two halves are not known at the same time:

  1. The half which is known before the type is compiled is written by a source generator: the s_Instance field and the Instance property which makes the instance the first time it is read. Nothing more is asked of the type than that it is declared partial.

  2. The half which is not known then is woven in by a Mono.Cecil post processor after the type was compiled. The instance which Unity made is not known to the property until something tells the field about it, and the message which does that is Awake for a MonoBehaviour and OnEnable for a ScriptableObject - messages a type may already have written, so they cannot be generated beside it and are woven into it instead. The post processor is a part of the compilation rather than something beside it, so a singleton is one in a player as well as in the editor.

What the generator needs to know about the project, which is the Resources path of a type for which no path was given, is written into obj/Singleton.txt by the editor half through the AssetDatabase. A path is therefore read once per change to the project rather than walked on every compilation.

Requirement

  • Unity: $\geqslant$ 2021.3.*
  • com.unity.nuget.mono-cecil: $\geqslant$ 1.11.6

Install

From NPMJS:

{
  "scopedRegistries":
  [
    {
      "name": "npmjs",
      "url": "https://registry.npmjs.org",
      "scopes": ["com.gatongone"]
    }
  ],
  "dependencies":
  {
    "com.gatongone.singleton": "1.0.0"
  }
}

From OpenUPM:

{
  "scopedRegistries":
  [
    {
      "name": "package.openupm.com",
      "url": "https://package.openupm.com",
      "scopes": ["com.gatongone"]
    }
  ],
  "dependencies":
  {
    "com.gatongone.singleton": "1.0.0"
  }
}
openupm add com.gatongone.singleton

From GIT:

{
  "dependencies":
  {
    "com.gatongone.singleton": "https://github.com/Gatongone/Singleton.git#v1.0.0"
  }
}

License

Singleton is released under the MIT License. Copyright (c) 2026, Gatongone