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!
#Editor Singleton gets replace in Playmode
1 messages · Page 1 of 1 (latest)
Small update, I just found out that reloading the scene when entering the playmode fixes this issue.
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
Yeah, that makes sense.
However, it seems to be the scene reload that fixes the issue, not domain reload.
Is this just something I'll have to have enabled from now on or is there a way to work around this by properly coding the singleton or something?
make sure to actually read what disabling domain reload does and what kind of extra work you need to do in order to avoid bugs like this https://docs.unity3d.com/6000.2/Documentation/Manual/domain-reloading.html
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
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?
just use the RuntimeInitializeOnLoad attribute on a static method to assign null, no need for the event
That does seem like a better approach.
I was reading more and ran into this, could that be useful in this case?
InitializeOnEnterPlayModeAttribute
It says it'll reset all the static fields when entering the play mode.
that is for an editor class
I see, thanks a ton for your help!
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!
I am just assuming, that your log gets cleared, because you ticked this one?
The playmodestatechange is just holding "EnteredPlayMode" which potentially gets cleared because the new state is not entered but "within" playmode. even if thats not covered by the Unity enum, it will for some reason fire another event that says, its playing now, like in this API:
https://docs.unity3d.com/6000.2/Documentation/ScriptReference/EditorApplication-isPlaying.html
Yes, you assumed correctly for the log clearing.
However, I am not understanding what your suggestion below is.
The issue isn't with the log being cleared, the code still isn't working, same as before.
Ah sorry, quickly jumped in here. What code is not working exactly? Like the Instance static one?
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.
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
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 :/
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?
I have certain editor scripts that rely on it.
On top of that, this implementation is supposed to support the edit mode, and another person ran into the same issue.
So might aswell fix it for everyone.
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.
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.
I'll get rider real quick to try to debug this and trace what exactly causes the creation, this confuses me so much :/
Because I set certain things in the editor, get references to components, set values etc, here
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
The fields it holds yes, the manager itself no.
I am using the UnityCommunity singleton implementation, that should be fine.
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.
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.
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.
And about the support of the singleton creation. If you call it from another script to create a new version of it, I totally get that, but in t that case of course the new version will not have any values from your previous version assigned in the inspector. Correct me if I my assumptions are mixed up here 😄
No, that's accurate.
When you create a new singleton it'll just hold default values.
I fully expect that, I am trying to avoid precisely that creation and deletion of the previous singleton.
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?
Yes of course.
I have to get off of my pc for a bit, not too long, will let you know what the logs are once I'm back.
ok wait, I might've just found the issue 🤦♂️
I would just like to run it by you if that's fine, I don't wanna make a PR that'll break otherstuff 💀
Curious what you found 😄
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.
Let me look at that later with a clear mind, currently fighting my own code issues here 😄
I made a PR, we'll see if maintainers object due to it causing some other issues.
Thanks once again for your help, means a lot!
https://github.com/UnityCommunity/UnitySingleton/pull/22