0
\$\begingroup\$

I'm trying to use the [ExecuteAlways]attribute to have a singleton both in edit- and -play modes. But the Awake() method sometimes is not called in the edit-mode. What could be the reason for this and how to fix it?


To make a short story long, I've got a not-so-new Unity project that I believe was developed before the age of ScriptableObjects, so they used MonoBehaviour to store game settings. And they used a kind of "Singleton" pattern that was not very stable and sometimes caused artefacts, such as creating an extra instance in edit mode which could often be saved with the scene accidentally and caused some other problems. The "Singleton" was written this way:

private static GlobalSettings instance = null;

public static GlobalSettings Instance
{
    get {
        if (instance == null)
        {
            instance = (GlobalSettings)FindObjectOfType(typeof(GlobalSettings));
            if (instance == null)
                instance = (new GameObject("GlobalSettings")).AddComponent<GlobalSettings>();
        }
        return instance;
    }
}

to fix it I've changed the code (and added the [ExecuteAlways] attribute):

[ExecuteAlways]
public class GlobalSettings : MonoBehaviour
{
    private static GlobalSettings _instance;
    public static GlobalSettings Instance => _instance;

    private void Awake()
    {
        if (_instance)
        {
            Destroy(this);
            throw new GameException($"The '{nameof(GlobalSettings)}' is a singleton!");
        }

        _instance = this;
    }
}

as I said Awake() is not called sometimes in edit-mode and the _instance remains null. Another problem is that even when Awake is called it happens after the first call of OnInspectorGUI() where the game settings are needed.

Any suggestions and considerations on how to make this code work stable are welcome!

\$\endgroup\$
0
\$\begingroup\$

I fixed it this way:

[ExecuteInEditMode]
public class GlobalSettings : MonoBehaviour
{
    private static GlobalSettings _instance;
    private static GlobalSettings _editorInstance;

    public static GlobalSettings Instance 
    {
        get
        {
            if (Application.isPlaying)
                return _instance;

            return _editorInstance;
        }
    }

    private static readonly string _singletonViolationMessage = $"The '{nameof(GlobalSettings)}' is a singleton!";

    private void Awake()
    {
        Log.Caller(Application.isPlaying);

        if (Application.isPlaying)
        {
            if (_instance)
            {
                Destroy(this);
                throw new GameException(_singletonViolationMessage);
            }

            _instance = this;
        } 
        else
        {
            if (_editorInstance)
            {
                Destroy(this);
                throw new GameException(_singletonViolationMessage);
            }

            _editorInstance = this;
        }
    }
}

Some of my conclusions and conjectures:

  1. Awake in edit mode is fired only once. Even if I change the code and then it is recompiled by Unity the Awake method is not called again. Still don't understand why...

  2. When Awake is called in edit-mode the _instance is set correctly. Then I press "Play" and it is destroyed and rewritten by a new instance of the play-mode. When I go back to the edit-mode the "play-mode-instance" is destroyed but the `Awake' method is not called anymore and it remains so-called "null". Thus, it must be stored separately in two different instances for the edit- and play- modes.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.