Jump to content
  • Advertisement
Sign in to follow this  
Moehammered

Am I using singleton when I shouldn't?

This topic is 2600 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Now today whilst riding the train I started reading up on singletons (I had been taught about it from my college teacher a year ago) and started digging into the theory behind the design pattern.
Apparently singleton is a bad approach, and a lot of the discussions I've read had been saying stuff like 'it's bad for unit testing' or stuff like (BTW what exactly is meant by unit testing in relation to Game Development?).

Now the thing is I have a situation where I can't find any other pattern or implementation method where singleton wouldn't be the best approach. Currently I'm making a state based game engine to help with transitioning between menu and gameplay and anything else which can be considered it's own 'game state' as to only have ONE instance of a gamestate running at one time. So e.g. hit play, go to gameplay, delete the previous state from memory(to keep it from leaking or anything tragic otherwise) and than continue gameplay, hit exit in game, return to menu, delete game play from memory. So rinse repeat this. The way I do it is I have a 'state' which any class inherits to maintain base functionality. Than if I want to switch, all I do in the game is call StateSystem::switchStates(new Menu()); and it will go to a menu. Is this bad and if so how have others managed to do the same? From switching to game to menu and not having to change things inside the main while loop?
So enough of my rant here's the code :)

main.c



Display *display= NULL;
GL_Controller *myGLController = NULL;
StateSystem *stateSystem = NULL;
State *tempState = NULL;

void setupDisplay()
{
display = Display::createInstance();
display->initialise(640, 480, 24);
display->createPerspectiveProjection(60, 1, 5.0f, 500);
}

void setupGLController()
{
myGLController = GL_Controller::createInstance();
myGLController->initialiseGLController();

printf("OpenGL Version: %f\n", myGLController->returnGLVersion());

if(myGLController->checkVBOOn())
printf("Vertex Buffer Objects Available: True\n");
else
printf("Vertex Buffer Objects Available: False\n");

if(myGLController->returnGLVersion() < 1.5f)
glClearColor(0, 0, 1, 0);
}

int main(int argc, char* args[])
{
float rot = 0;
setupDisplay();

setupGLController();

SDL_Event event;

Timer *timer = new Timer();
stateSystem = StateSystem::create();

bool switched = false;
stateSystem->switchStates(new FileReadTest());

glEnable(GL_CULL_FACE);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

while(1)
{
timer->processTime();
timer->processFrameRate();

if((SDL_PollEvent(&event) && event.key.keysym.sym == SDLK_ESCAPE))
break;

rot += 0.1f;

glTranslatef(-25,0,-80);

stateSystem->updateState();
display->updateDisplay();
}

glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
timer->saveHighestFrameRate();

return 1;
}



StateSystem .h


#include "State.h"

class StateSystem
{
public:
StateSystem();
virtual ~StateSystem();

void initialise();
void updateState();
void shutdownState();

void switchStates(State*);

static StateSystem* create();

protected:
private:
static StateSystem* instance;
State *current;
bool updatePrint;
};



StateSystem.cpp

#include "StateSystem.h"
#include "stdio.h"

StateSystem* StateSystem::instance = 0;

StateSystem::StateSystem()
{
current = 0;
updatePrint = false;
}

void StateSystem::initialise()
{

}

void StateSystem::switchStates(State* newState)
{
if(current != 0)
{
delete current;
current = 0;
}

current = newState;
current->initialise();
}

void StateSystem::updateState()
{
current->update();
}

StateSystem* StateSystem::create()
{
if(instance==0)
{
instance = new StateSystem();
}

return instance;
}

StateSystem::~StateSystem()
{
//dtor
}



State.h


class State
{
public:
State();
virtual ~State();

virtual void initialise()=0;

virtual void update()=0;

protected:
SDL_Event event;
private:
};

Share this post


Link to post
Share on other sites
Advertisement

Now the thing is I have a situation where I can't find any other pattern or implementation method where singleton wouldn't be the best approach.


Just using an actual global variable? 'Cause that's what a Singleton is, just wrapped up in a fancy package. You might as well just make a global variable for the game state, and when the state changes just reassign it. You can't have two instantiations* of the global variable, so it's just as "single" as the singleton, plus it's less ambiguous in my eyes, as it doesn't have "pretty" cruft mixed with it.

Or you could use a stack of states, and the one on top is the current state, and then just pop from the top of the stack to go back, or push onto the stack to go forward.

Or you could have a switch statement that reads a single enum for the game state (i.e. E_MAIN_MENU, E_IN_GAME, E_PAUSED) and just switches the logic based on the game state.

There's lots of options. I have yet to see an instance where a singleton is the best (I don't want to say that it's always the wrong choice, as such blanket statements are dangerous, however, I really haven't ever seen an instance where a singleton is the best solution).


*How the heck is that word spelled? It's a word, right?

Share this post


Link to post
Share on other sites
Instantiation is a word and I think you did spell it right :P

As for what you've said I actually hadn't thought of using the enum approach, (which I have actually done before >.<)

but than the enum state would have to have a global variable and inside the switch all states would have to be pre-defined, would they not?

the global variable for 'state' is actually a good idea now that I think about it, but if for example I went and had 'Menu' and it's pointer is the same as the global variables pointer 'currentState' and I call delete currentState, wouldn't I then be unable to continue past that line to re-instantiate the currentState into say 'Game' ?


void Menu::switchState(State*)
{
delete currentState;
//This line won't fire off due to Menu's pointer referencing a NULL address?
}

Share this post


Link to post
Share on other sites

the global variable for 'state' is actually a good idea now that I think about it, but if for example I went and had 'Menu' and it's pointer is the same as the global variables pointer 'currentState' and I call delete currentState, wouldn't I then be unable to continue past that line to re-instantiate the currentState into say 'Game' ?


I'm not sure what you're asking here, so I'll hit both possibilities :-)

  • Your Menu object's pointer will never be the same as the pointer to some totally different object in your program. We'd all be in for a disaster if that were possible!
  • If your currentState object is pointing to a Menu object and you delete it while still using that Menu object, then yes, you have a bug. This is known as a dangling reference.

    Of course, in the latter case, the question arises: why is your menu of all things deleting game states?


Share this post


Link to post
Share on other sites

[quote name='Moehammered' timestamp='1319520920' post='4876620']
the global variable for 'state' is actually a good idea now that I think about it, but if for example I went and had 'Menu' and it's pointer is the same as the global variables pointer 'currentState' and I call delete currentState, wouldn't I then be unable to continue past that line to re-instantiate the currentState into say 'Game' ?


I'm not sure what you're asking here, so I'll hit both possibilities :-)

  • Your Menu object's pointer will never be the same as the pointer to some totally different object in your program. We'd all be in for a disaster if that were possible!
  • If your currentState object is pointing to a Menu object and you delete it while still using that Menu object, then yes, you have a bug. This is known as a dangling reference.

    Of course, in the latter case, the question arises: why is your menu of all things deleting game states?



    [/quote]

    State *currentState = new Menu();

    void Menu::update()
    {
    if(Play button is pressed)
    {
    currentState = new Game(); // This would leave garbage behind as the pointer for currentState has changed has it not?
    /*OR*/
    delete currentState;
    currentState = new Game();
    //But now since this is a function call inside the Menu class, and the currentState has been deleted,
    //than the menu would not be able to run this piece of code because update is a function inside the menu class.
    }
    }


    State *currentState

    So than let me clarify, currentState would be a global variable which would constantly be used to update whatever is actually instantiated be it a menu, game, cinematic, etc etc.
    Menu contains the code to switch the currentState's pointer reference to a new object. The singleton I have does this but acts as a mediator interface to make sure that the instance variable won't become null
    and that no matter which type of state is calling it, it will initialise correctly. I do apologise if I'm unclear but I really want to see if there is another convinient and non-bad-habit developing(for lack of a word) way of doing this.

Share this post


Link to post
Share on other sites
Make currentState a smart pointer instead of a raw pointer. Then it'll take care of the replacement semantics for you.

Share this post


Link to post
Share on other sites
While the original intention of a "singleton" is to guarantee 'only 1 instance', singletons are most often used like global variables (bad).
Best to avoid them :)

Looking at your code, it seems like you're making it too complex for your use case. It's perfectly fine to not use classes.





bool menu_loop(Renderer a, K b, L c, ...) { }
bool game_loop() { }

Renderer renderer;
while(!quit)
{
while(menu_loop(renderer, a, b, ...)) { }
while(game_loop()) { }
}



For any complex state stuff, check out coroutines (Lua has a good implementation). Coroutines minimize boilerplate execute(), entry/exit functions, having to keep track of stuff across execute() calls.

Share this post


Link to post
Share on other sites
Smart pointer seems like the thing I should do than.

But just for discussion sake, I seem to find singleton convenient, is there anything wrong with this? What are the pitfalls I can expect and if so would they be very bad to the point they would cause negligence in my code? Of course I don't like to overuse them because well... if anyone's used Ogre3D IT IS HELL TO USE THEIR SINGLETON DESIGN!! :/

I do however like it because I can create something I need everywhere with functionality attached to it. I'm a bit put down cause I feel I'm somewhat making myself a bad programmer by using this pattern now... :(

Share this post


Link to post
Share on other sites

While the original intention of a "singleton" is to guarantee 'only 1 instance', singletons are most often used like global variables (bad).
Best to avoid them :)

Looking at your code, it seems like you're making it too complex for your use case. It's perfectly fine to not use classes.

For any complex state stuff, check out coroutines (Lua has a good implementation). Coroutines minimize boilerplate execute(), entry/exit functions, having to keep track of stuff across execute() calls.


Hmm never heard of coroutines before, sounds very interesting :)

But my use case is I'd like only one game element ever in memory and running. Doesn't a singleton fulfill this?

Share this post


Link to post
Share on other sites
But just for discussion sake, I seem to find singleton convenient, is there anything wrong with this?
The first 4 lines of code that you posted create 4 global variables://main.c
Display *display= NULL;
GL_Controller *myGLController = NULL;
StateSystem *stateSystem = NULL;
State *tempState = NULL;
The problem with singletons is that they are global variables and globals are evil.
...but seeing as you're very comfortable writing code that's full of globals, then you've got no need to worry about using singletons as well!

Globals/singletons are bearable on small projects. On large projects or reusable software, they make it impossible to reason about the code-base. At work (as an engine programmer), I would be dragged into an office and be given a stern talking to if I used these kinds of patterns, because of all the pain and suffering they cause.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

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

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!