#Editor Singleton gets replace in Playmode

1 messages · Page 1 of 1 (latest)

peak quartz
#

Hey, I am using this singleton implementation: https://github.com/UnityCommunity/UnitySingleton/
It works really well, except for when I use it in editor and then that same script tries to call it during playmode in Update.
Here's a detailed github issue if you would like more info: https://github.com/UnityCommunity/UnitySingleton/issues/21
As seen in that issue, the singleton has a higher execution priority, initializes in Awake and all that, and still, when the script calls it from Update, the singleton gets replaced.
Here's the implementation of that singleton: https://github.com/UnityCommunity/UnitySingleton/blob/main/Runtime/Scripts/MonoSingleton.cs
Lastly, here are:
Script where I implement the singleton: https://github.com/NNStdios/Katana/blob/master/Katana/Assets/Scripts/Colosseum/ColosseumSceneManager.cs
Script that calls the singleton and causes the issue: https://github.com/NNStdios/Katana/blob/master/Katana/Assets/Scripts/Player/Camera/CameraHandlerScript.cs
This genuinely just seems like a Unity bug, but I could be mistaken.
Would somebody happen to have a clue as to what's happening?
Please @ me and thanks in advance!

GitHub

The best way to implement singleton pattern in Unity. - UnityCommunity/UnitySingleton

#

Small update, I just found out that reloading the scene when entering the playmode fixes this issue.

night cloak
#

i see in the github issue you've narrowed it down to being an issue when domain reload is disabled and that makes perfect sense. when domain reload is disabled static values are not reset when entering play mode. so the static instance field still references the previous version of the singleton, which is now destroyed at this point (so equates to null), therefore the a new instance is created

peak quartz
night cloak
#

the reason scene reloading fixes that is because all objects are destroyed then reloaded so the singleton is manually recreated through the standard Awake method before anything interacts with it, it's really just a bandaid fix for the domain reload not happening

peak quartz
#

I see, I just looked at the docs you sent and just wanna make sure I am getting this right.
I should listen to the play mode state changed event and set the singleton to null there, correct?

night cloak
#

just use the RuntimeInitializeOnLoad attribute on a static method to assign null, no need for the event

peak quartz
night cloak
#

that is for an editor class

peak quartz
#

I see, thanks a ton for your help!

peak quartz
# night cloak just use the RuntimeInitializeOnLoad attribute on a static method to assign null...

Hey, I've been playing around with different approaches due to RuntimeInitializeOnLoad not being called on classes that inherit the singleton class.
I would like to make it so that the end user doesn't have to do any additional work when implementing that singleton which the above approach would require.
So I decided to go with this:

#if UNITY_EDITOR

        private static void OnPlayModeChanged(PlayModeStateChange state)
        {
            Debug.Log("HERE");
            if (state == PlayModeStateChange.ExitingEditMode ||
                state == PlayModeStateChange.ExitingPlayMode) instance = null;
        }

#endif

        public static T Instance
        {
            get
            {
                if (instance == null)
                {
#if UNITY_6000
                    instance = FindAnyObjectByType<T>();
#else
                    instance = FindObjectOfType<T>();
#endif
                    if (instance == null)
                    {
                        GameObject obj = new GameObject();
                        obj.name = typeof(T).Name;
                        instance = obj.AddComponent<T>();
                        instance.OnMonoSingletonCreated();
#if UNITY_EDITOR
                        EditorApplication.playModeStateChanged -= OnPlayModeChanged;
                        EditorApplication.playModeStateChanged += OnPlayModeChanged;
#endif
                    }
                }
                return instance;
            }
        }

However, this doesn't fix the issue.
When I click play, it logs HERE, but that quickly gets cleared and I get the logs seen in the screenshot.
Am I doing something wrong, or do you have a suggestion for an even better approach by any chance?
Oh, I also tried to apply a different default execution order but that didn't help either:

[DefaultExecutionOrder(-100)]
public abstract class MonoSingleton<T> : MonoBehaviour, ISingleton where T : MonoSingleton<T>

Thanks in advance!

faint wraith
#

I am just assuming, that your log gets cleared, because you ticked this one?

peak quartz
faint wraith
#

Ah sorry, quickly jumped in here. What code is not working exactly? Like the Instance static one?

peak quartz
# faint wraith Ah sorry, quickly jumped in here. What code is not working exactly? Like the Ins...

In short, I have a singleton that's supposed to work in both the edit and play mode.
However, when used in edit mode, when the script tries to use it in play mode, the singleton gets destroyed and a new one is created.
So therefore, I tried to set the singleton to null when changing the play state, but singleton still gets destroyed.
I had to skip quit a few details because there's a lot, you can find all the info above if you are interested.

faint wraith
#

Ah, you want a persistent singleton in your scene? Does it have to be in the scene or could it be a static class not inheriting from monobehaviour and just abuse a runtime object to do monobehaviour stuff?

#

The problem is, that every gameobject will be "recreated" when entering playmode, there is no consistency from edit mode to playmode for your objects, because otherwise, you could mix up playmode changes with edit mode changes

peak quartz
# faint wraith The problem is, that every gameobject will be "recreated" when entering playmode...

Sadly can't just use a regular singleton, I rely on functions such as awake, reset etc. to perform certain actions.
I don't mind it being a different object once you enter the playmode, that's perfectly fine by me.
Looking at the MonoSingleton implementation, it should just look for the first object T in the scene to find the manager, that's why I tried setting it to null before entering the playmode but it didn't seem to work :/

faint wraith
#

Yeh I am still guessing, that your setting to null does not work, because it is already null as soon as it enters playmode. Why do you need it to be in edit and play mode persistently in the first place?

peak quartz
#

Also, the assumption you made that it's null when you enter the playmode seems to be false.
I added logs in play, awake and getter functions and these are the logs.
Entering the playmode happens at the one I selected, and the rest happens from there.

faint wraith
#

Of course it is not null anymore as soon as it is created with a static instance, but the EditMode version of it is being recreated, that was my assumption. I still try to understand your setup, how editor scripts can rely on a specific version of an instance instead of just ANY instance of this class.

peak quartz
#

I'll get rider real quick to try to debug this and trace what exactly causes the creation, this confuses me so much :/

peak quartz
dusty stone
#

I haven't looked closely but one thing to consider is that your ColosseumSceneManager has been written in an extremely dangerous way by using null coalescing operators on these public static fields, which will not do what you think it does

peak quartz
faint wraith
#

Sounds to me like you are mixing up runtime references with static instances here. You want the specific version you set up in your scene but still creating a new version of it. You probably should do your instance check inside a virtual awake instead of the constructor itself.

#

If you got a version of your manager already in the scene, this is your "instance" already. so you rather should check, if at any point a new instance checks in its own awake function, if there is already a version of it, and if so, destroy itself.

peak quartz
# faint wraith If you got a version of your manager already in the scene, this is your "instanc...

Yes, what you suggested here is correct.
The singleton implementation I am using is not an implementation of exactly what I need, but it should support it.
What I need can be made with a single Awake that would set the instance to this, and a getter that would find the first object of that type and set the instance.
The singleton implementation, on top of that, supports creating a new singleton if one is not found.
However, the issue here is that it doesn't find the already existing singleton when entering the play mode, or at least something similar to that happens.

#

I am not trying to solve this because there's no other way out, the main reason I am trying to do it is to fix the implementation many people use, and possibly make someone's life easier in the future.
This is unexpected behavior regardless of the implementation being exactly what I need in my scenario.

faint wraith
#

I think, the culprit is the constructor trying to "fix itself" here. Thats just my suggestion as I am using another singleton pattern with just a simple awake function to destroy all others or itself, if not desired to update the instance. I appreciate your effort to help others, but I just think, its an issue within itself and needs some refactor if there have been multiple people running into the same issue. But I also do not have the time right now, to dive into it more sadly. I am just trying to figure out, what the advantage if THIS approach is right now versus for example mine and what I might be missing here.

faint wraith
peak quartz
faint wraith
#

Okay, got it. So the issue for me is still, you are holding references to YourManager.Instance which will automatically check for the instance, if not there, create a new one. I finally got, what the issue is here 😄 Which makes me wonder, why your executionorder fix is not working tho

#

can you log in that constructor to see, what is actually creating it?

peak quartz
peak quartz
#

I would just like to run it by you if that's fine, I don't wanna make a PR that'll break otherstuff 💀

faint wraith
#

Curious what you found 😄

peak quartz
#

Ok, so this is what I think is the cause: https://github.com/UnityCommunity/UnitySingleton/blob/9416bf62bb893a1d416ff3c5ef83275164373f11/Runtime/Scripts/MonoSingleton.cs#L68
To sum it up, if the instance is not null(the playmode just started), it'll directly jump to destroying itself.
What I did is turn the else into an else if, that also checks whether instance != this, and only then destroys the object:

protected virtual void Awake()
{
    if (instance == null)
    {
        instance = this as T;

        // Initialize existing instance
        InitializeSingleton();
    }
    else if (instance != this)
    {

        // Destory duplicates
        if (Application.isPlaying)
        {
            Destroy(gameObject);
        }
        else
        {
            DestroyImmediate(gameObject);
        }
    }
}

I am not sure if this would introduce some other issues, my brain is way too fried atm to think on a large scale tbh.
If it looks good to you too, ig I'll just make a PR.
Also, this might also eliminate the need for the OnPlayStateChanged method, I'll have to test that tho.

faint wraith
#

Let me look at that later with a clear mind, currently fighting my own code issues here 😄

peak quartz