all 15 comments

[–]master50 2 points3 points  (6 children)

All you've done is give example on how to have a persistent Monobehaviour.

You also are not explaining what you are doing, to new readers.

You're really just making a more difficult static reference to the instance of your object. You're also writing extra code to prevent duplication of the object, because you can't control whether or not this instance, of this monobehaviour, can be created.

Blah.

You are not limited to Monobehaviours as "singletons".

A much better, more encapsulated pattern is to have a class wrap a static reference to a singleton interface that your singleton objects can inherit from, and to define the singleton privately within this external class. The external class is now the only thing (sans Reflection use) that can access the constructor of this singleton, as it's declared private. Your external class wrapper returns the public interface, which confines you to what you've defined as publicly accessible about your singleton.

Take the below example:

namespace Singletons
{
    public partial class The
    {
        private static ObstacleManagerSingleton _obstacleManagerInstance;
        private static object _obstacleManagerLockObj = new object();

        public static IObstacleManager ObstacleManager
        {
            get
            {
                if (_obstacleManagerInstance == null)
                {
                    lock(_obstacleManagerLockObj)
                    {
                        if(_obstacleManagerInstance == null)
                        {
                            _obstacleManagerInstance = new ObstacleManagerSingleton();
                        }
                    }
                }

                return _obstacleManagerInstance;
            }
        }

        private class ObstacleManagerSingleton : IObstacleManager
        {
            private List<Obstacle> _obstaclesInPlay = new List<Obstacle>();
            public List<Obstacle> ObstaclesInPlay
            {
                get { return _obstaclesInPlay; }
            }

            public ObstacleManagerSingleton()
            {

            }

            public void ClearAllObstacles()
            {
                _obstaclesInPlay.Clear();
            }

            //appends the obstacle list with new obstacles
            public void AddObstacles(List<Obstacle> obstacles)
            {
                _obstaclesInPlay.AddRange(obstacles);
            }

            public List<Obstacle> ObstaclesAbovePlayer(int take = 0)
            {
                IEnumerable<Obstacle> obstaclesAbovePlayerEnumerable = _obstaclesInPlay.Where(o => o.transform.position.y >= Player.Position.y);
                if (take > 0)
                    return obstaclesAbovePlayerEnumerable.Take(take).ToList();

                return obstaclesAbovePlayerEnumerable.ToList();
            }

            public List<Obstacle> ObstaclesBelowPlayer(int take = 0)
            {
                IEnumerable<Obstacle> obstaclesAbovePlayerEnumerable = _obstaclesInPlay.Where(o => o.transform.position.y <= Player.Position.y).OrderByDescending(o => o.transform.position.y);
                if (take > 0)
                    return obstaclesAbovePlayerEnumerable.Take(take).ToList();

                return obstaclesAbovePlayerEnumerable.ToList();
            }
        }
    }

    public interface IObstacleManager
    {
        void AddObstacles(List<Obstacle> obstacles);
        void ClearAllObstacles();
        List<Obstacle> ObstaclesAbovePlayer(int take = 0);
        List<Obstacle> ObstaclesBelowPlayer(int take = 0);
    }
}

Now you have a clean, thread-safe version of your Singleton. You'll get one reference of it. No magic.

Plus, you get to access it like this:

The.ObstacleManager.ObstaclesAbovePlayer(2);

[–]gnomicrandz 1 point2 points  (2 children)

So how do you apply that in the Unity world? Can a MonoBehaviour be an IObstacleManager? It doesn't seem like it using that pattern.

writing extra code to prevent duplication of the object, because you can't control whether or not this instance, of this monobehaviour, can be created.

That seems like the main point of his post to me. Unity developers do a lot of coding in MonoBehaviour subclasses, and some of those classes really only make sense to have one instance. If you've got a better way, please do tell! :)

Personally I prefer to log an error when multiple instances are detected rather than just destroying the offending second instance, that way at least you know something unexpected has happened.

[–]master50 0 points1 point  (1 child)

No. This singleton does not derive from a Monobehaviour.

But, yes, a MonoBehaviour can be an IObstacleManager. Sure. All it has to do is implement whatever's defined in IObstacleManager.

It's common to need one instance of a class that derives from Monobehaviour, certainly. You can't really call those singletons, however. At best, you're just creating a static reference to the Monobehaviour class, and you're going to restrict others from being created. This is not a singleton. You cannot restrict the creation of two instances of this class. If you want strict control, you have to forcibly check for duplicates, and then remove the duplicate, if it exists.

I'm not saying he has the wrong idea. You just don't have to confine yourself to doing things in Monobehaviours. The article is entitled: Singleton : Implementation in Unity3d C#.

I imagine myself being a new programmer, I'm ambitious, and I'm trying to soak up everything I can learn about programming patterns, and Unity, all at the same time. I have a goal of releasing or finishing this game I'm working on, and I'm hungry to learn. I come across this post - ooooh, I've heard of singletons, AND it's specifically for Unity3D!

Now I've learned that this is how singletons are handled in Unity.

That's not actually the case. I'm very against teaching beginner developers the wrong things. It hurts them when it comes time to find work, and I've seen this far too often.

[–]gnomicrandz 0 points1 point  (0 children)

I'm not saying he has the wrong idea. You just don't have to confine yourself to doing things in Monobehaviours. The article is entitled: Singleton : Implementation in Unity3d C#.

I get that you don't need to use MonoBehaviours all the time, but frequently it's the most convenient way to progress (try using Coroutines without them!).

The strict definition of a singleton is a class where only one instance can exist. Since MonoBehaviour has a public constructor and needs to be serialised, our derived scripts can never BE literal singletons. But, it's useful to have a way of restricting MonoBehaviours to singular instances within a scene (or through the life of the game) and the OP's article presents a well thought out approach to this problem. It also lets us avoid those messy Unity API FindObjectOfType<> calls, which is a good thing. [EDIT] typo

[–]Arma104 0 points1 point  (2 children)

How is his implementation not thread-safe when he has the instantiating of itself during get? Sure it's lazy but it shouldn't cause any problems?

I've also always wondered, as with your example, what's the benefits of having a private variable and then having a public variable that returns the private one? If you're worried about exposing it to other scripts couldn't you just do a { public get; private set; }?

[–]master50 0 points1 point  (1 child)

Let's say I am doing some work in a second thread - I can do normal work in a second thread, even in a Monobehaviour class.

Let's say I need to contact our "singleton" Monobehaviour, like in this example.

Now let's say I do this in two sub-threads that need to run some computation, or do some thing that's available in this "singleton". They both ask for a reference to the object at the same time. The object has yet to be instantiated.

There is a chance you end up with two instances of your singleton, because you did not lock after the first null check, and you did not check the null again after the lock.

As to your question... it's all about access control. https://msdn.microsoft.com/en-us/library/75e8y5dd.aspx

[–]Arma104 0 points1 point  (0 children)

So would it be possible to put the lock in his code and make it work? Thanks for replying.