Why did we go back to teaching the bad Singletons?

Awhile ago, we had some huge bugs in our singleton code that was affecting a lot of people in several projects. After some investigation and speculation, we figured out the problem was that when you try to Find a singleton in Start() or Awake(), you can often find the wrong one if there were two in the first frame (such as one in the current scene and one carrying over from the last).

It was a tough problem. The instructors and student instructors didn’t seem to know how to fix it, but resorted instead to finding the singleton again whenever they needed it (because after the first frame, the second singleton should have destroyed itself back then).

I and others came up with more convoluted ways of working around the problem, which were tough to explain to new people working on their first projects.

As far as I know, it was Sam who came up with the elegant solution that worked perfectly, used concepts already taught in beginner courses, and required only one additional line of code. It was brilliant. And it helped teach some quirks of Unity.

That line simply set the object inactive before calling for it to be destroyed. This way, during that weird transition time when it hasn’t been destroyed yet, no Find or search of any kind will reference it. Instead, a Find will go to the only remaining active singleton in existence. So all your scripts trying to initialize their references on Start() or Awake() will get the correct singleton.

To be honest, this is actually more effective than destroying the singleton. Destroying the singleton after it’s been declared inactive is more of a formality and not particularly necessary.

So I’m surprised that we’ve gone back to teaching a singleton that does not deactivate itself upon discovering it’s older brethren, but instead sticks around and mucks with FindObject searches.

3 Likes

It’s a good point, but I think the idea was just to make the examples as simple as possible. While the problem you describe can happen, it doesn’t always happen. I could also say the same thing regarding unit testing; Sam never really explains what that is. Some of us are familiar, but others I am sure are not.

At the end of the day it’s a judgement call the author made; how many edge cases to cover in each topic. I am sure they appreciate the feedback, however.

There’s no perfect method implementation. About as close as I came was a public static getter which helps protect against those pesky race conditions which is most of the cause of these singleton issues.

That all being said, you don’t need to use singletons. It’s a tool that you can decide to use if it’s appropriate for you. I’d rather have the tool in my toolbox and not need it than need it and not have it.

I just checked the new beginner 2D course, and the correction is taught there so I guess that’s what really matters.

image

When I do use a Singleton (rarely, but sometimes it is the best tool for the job), I prefer never caching externally, only in an instance model…

private static MySingletonClass instance;
public static MySingletonClass Instance =>instance; //prevents other classes from changing it

void Awake()
{
    if(instance) Destroy(gameObject);
    else instance=this;
}

To avoid race conditions, never access MySingletonClass.instance in the Awake method of any other class. You can always safely access it in OnEnable(), Start(), or Update().

1 Like

Privacy & Terms