Jump to content
  • Advertisement
povilaslt2

Questions regarding singletons and when to use them

Recommended Posts

Hello, i'm currently developing spaceshooter game and I encountered few problems with game code design.

I have Application class where all managers are.

class Application
{
    public:
    ~Application();
    Application();
    void addShader(const std::string&, std::shared_ptr<Shader>);
    void loadTexture(const std::string&, const std::string&, int, int, glm::vec2);
    std::shared_ptr<Texture> getTexture(const std::string&);
    inline std::shared_ptr<Font> getFont(const std::string& name) { return fontManager->getFont(name); }
    void loadFont(const std::string&, const std::string&);
    inline GameState getState() const { return gameState; }
    inline void setState(GameState state) { gameState = state; }
    inline std::shared_ptr<InputManager> getInputManager() const { return inputManager; }
    inline std::shared_ptr<Renderer> getRender() const { return renderer; }
    private:
    std::shared_ptr<Renderer> renderer;
    std::shared_ptr<TextureManager> textureManager;
    std::shared_ptr<InputManager> inputManager;
    GameState gameState;
    std::shared_ptr<FontManager> fontManager;
};

But problem is that I need to access in example TextureManager class somewhere in "OnCollision" method to change texture of collider. How do I do that? Do I create TextureManager singleton or what?

Edited by povilaslt2

Share this post


Link to post
Share on other sites
Advertisement

If an object needs a reference to the TextureManager to do its job, then give the object a reference to the texture manager. You can pass it in when the object is created. Better still, have the object grab these textures ahead of time, so that when the collision happens, it already has them ready for the change, without needing access to the TextureManager.

Share this post


Link to post
Share on other sites

A Singleton is probably a bad choice for this; it's (unfortunately) quite a common choice, but that's really just a lazy solution.

The Singleton is appropriate if (and only if):

  • You require global access to the object, and
  • There must only be a single instance of the object; it would be an error for there to be more than one.

You don't need global access in this case, that's just a convenience to avoid passing a reference to an object of more local scope.  I would do as Kylotan has suggested, and simply pass the object a reference to the texture manager.  If you really don't want to do that for some reason then just make the object global rather than a Singleton, because you still don't meet the second requirement; it would not be an error for a second TextureManager to exist, and could, in fact, be useful in future.

 

I've honestly only seen one example of a Singleton that really met the single instance requirement for the pattern: it was an object encapsulating a hardware controller, and if a second object were created it could potentially send conflicting commands to the hardware and cause errors or even damage the hardware.  Even in that case, the global access was really laziness rather than necessity; references could have simply been passed around.  There's a reason the Singleton is often considered an anti-pattern; there's a good chance you'll actually never come across a situation where it's ideally suited.

Share this post


Link to post
Share on other sites

If you really want to have global access to some managers, you can use a service locator pattern:

// Code not tested!
class ServiceLocator {
private:
	static TextureManager *_textureManagerInstance;
public:
	template<class T>
	static T* get();

	template<class T>
	static void set(T* instance);
};

ServiceLocator::_textureManagerInstance = nullptr;

template<>
TextureManager *ServiceLocator::get<TextureManager>() {
	return _textureManagerInstance;
}

template<>
void ServiceLocator::set<TextureManager>(TextureManager *textureManagerInstance) {
	_textureManagerInstance = textureManagerInstance;
}

TextureManager *textureManager = ServiceLocator::get<TextureManager>();

// and somewhere in the init-phase:

ServiceLocator::register<TextureManager>(textureManagerInstance);

But try not to use a hashtable for storing the instances, just use a static field for each manager and use template call for each type.

This way you have one central place storing all global instances and must not think about it anymore.

 

When you get multiple instances for a manager, then this wont work obvisouly.

Edited by Finalspace

Share this post


Link to post
Share on other sites
40 minutes ago, Finalspace said:

If you really want to have global access to some managers, you can use a service locator pattern:

Your pattern is just global object storage disguised as a service locator, so I'd say the same "global objects" rules apply to anything you store in here.

Share this post


Link to post
Share on other sites

Service locators are to singletons as singletons are to globals; one step improved, but still pretty bad.

I mean, I've used service locators before when hacking something together quickly, but they have almost all the same problems that a singleton does:

  • global access to an object leading to high coupling
  • global instances of an object meaning more shared state
  • poorly-defined object lifetimes
  • (usually) superfluous code enforcing that you only ever have 1 object, when there may be times you can make use of multiples

Basically, I try to take a step back when thinking about code like this. If I find myself thinking "lots of bits of the code need access to object X", I know that is not really true, and so I try to find one of several strategies for resolving that. e.g.

  • if object X is basically a resource provider (e.g. TextureManager), can we just extract what we need at initialisation time, and pass those objects in directly, instead of querying for them later?
  • if object X is basically an event sink (e.g. SoundPlayer), or an event source (e.g. InputManager) can I just connect to it via generic events/signals/delegates/std::functions/whatever, performing the connections once and then never needing to refer to the object again?
  • if object X is some sort of helper object which doesn't maintain any state, can I make the methods into free functions that can be composed arbitrarily (C, C++, Python, Javascript)? Or can I change the object into a static class (e.g. in C#) for similar effect?
  • if object X is a combination of the above things, or is just accessed everywhere because it performs a lot of different roles, can I split it into separate objects X, Y, Z, each of which might be easier to handle?

Share this post


Link to post
Share on other sites

One more question. Can I just add whole Application object reference when creating an object? Because I will need it to create object on collision for example and even more.

Edited by povilaslt2

Share this post


Link to post
Share on other sites
1 hour ago, Alberth said:

Your pattern is just global object storage disguised as a service locator, so I'd say the same "global objects" rules apply to anything you store in here.

Sure it is. Service locator just bundles a bunch of statics together. Look at java, they use it all over the place - especially with hashtables mapped to named instances. Absolutly great for performance...

I personally would never use such a pattern, because i rather pass the instance around.

If i require more than one instance, there must be something wrong with my design and i need to decouple them by making them independent. Or in worst case use a context state structure which bundles the instances i need together.

 

And keep in mind:

Globals are evil, the evil inside the evil of the evil itself from the core of all evil. Just use a class static field -> Its the better global after all... or not?

Seriously globals are fine, when you have just a couple of them and know how to use them properly.

My applications has at max two globals, one for logging and one for storing operating system states.

 

23 minutes ago, povilaslt2 said:

One more question. Can I just add whole Application object reference when creating an object? Because I will need it to create object on collision for example and even more.

Please give more details, i dont understand the question.

Edited by Finalspace

Share this post


Link to post
Share on other sites

I mean instead of referencing TextureManager object in game object constructor I would reference Application object which is shown in first post for more flexibility.

Share this post


Link to post
Share on other sites

Just seems like a bad idea. The game object doesn't need to know about everything in the Application so why pass the whole Application in?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!