1

I've been reading the top posts of stackoverflow and SE and all over the place it says how bad singletons are but I am unsure how to rewrite my code.

As of now I have two projects that bind into a gameEngine that each have a singleton class to access the content:

  • Singleton for retrieving/creating an instance of a 3d spatial grid and storing it as the active instance. Futher allows injecting own datatypes that conform to an interface and so can be de/-serialized with custom attributes into that active instance. Adding and removing objects from the active instance and bulk methods for the same thing.

  • Singleton for the main AI loop: Adding/Removing Units from the main AI loop, Injecting/Replacing additions to the main StateMachineProcesses that drive the AI. Most significant, what I hate about this second one: It needs access to the active grid instance from singleton #1 as one of the main aspects of the AI is the awareness of its surroundings and all worldobjects are stored in the database of project number one.

As there should always only be one active instance of these two things, are singletons a neccessity or is there a better way to redesign the whole thing?

5

Is singleton the right way to go...

No. A stateful, globally accessible, singleton (ie the singleton design pattern) is always an antipattern and there is always a better way of doing things.

The standard way of avoiding those singletons is to use dependency injection (DI). You could still have single instances of certain objects, you just pass them to each object, rather than making them globally accessible.

In your case, you could just go for very simple DI by passing the 3d spatial grid and AI loops to all other objects via their constructors. This is crude, but works around the need for singletons and so would immediately eg improve the ease with which your code can be tested.

| improve this answer | |
  • 2
    @user3488765, it all depends on whether you care about testing. When testing those Unit's, you want them to have different versions of "the world(DB)". At runtime, they'll all share the same one. They don't need to know either way though. They take what they are given. It's called the "tell, don't ask principle". You tell the Unit what it's using and it uses it. The game start up code is then solely responsible for creating that one instance and telling the various components about it for them to tell their components etc. – David Arno Dec 9 '16 at 9:57
  • 1
    There are always exceptions, and singletons (and it's cousins global variables, etc) are not always bad, just usually bad. Logging is one such example where a singleton type design can be appropriate. – whatsisname Dec 10 '16 at 6:02
  • 2
    @user3488765 Why do you assume that there will never be units that are part of two different worlds? (Once upon a time there was a little game called Minecraft, and the creator decided he wanted to support multiple worlds at once. Do you have any idea how long it took him to properly support multiple worlds, all because he made the world a singleton in the beginning?) – user253751 Dec 15 '16 at 2:39
  • 1
    @user3488765 It can't. But there can be different worlds that have different units in them. Maybe you'll make a multiplayer game server later and you'll want it to be able to run several games at once. That will be a lot easier if you didn't assume there's only one game. – user253751 Dec 15 '16 at 10:31
  • 1
    @user3488765, If you inject the world into the unit, then it doesn't matter how many worlds there are. Each unit gets given the world it needs to worry about. Whether that is one world or 1,000s isn't important to that unit; it only knows about the world it is told about. Thus why it's good to "tell; don't ask", ie use injection, not global singletons. – David Arno Dec 15 '16 at 10:41
-1

There are a few things here.

Firstly, if you have resource heavy component that you only need one (or a limited number) of, then a Singleton pattern is perfectly sensible. As described, your two uses look like good candidates for singletons.

Note, that's not the same as saying they should be globals. That's a different question (which I'll come to). It may be sensible, for example, to have a singleton 3d grid and explicitly pass it to your singleton AI loop.

Having globals is generally a bad idea, or more specifically having mutable globals is a bad idea. As both of your components are clearly mutable this would affect that decision. I won't go into detail about why globals are bad as there is no shortage of commentary on the 'net about this subject.

However, what doesn't often get reported is why you might want to have mutable globals despite their significant downsides.

If you don't have globals, then you have to pass a reference to the object to each function/method that must access it. Evidently, this takes up an argument slot. In certain language ABIs (notably some C and C++), the first few arguments may be passed in registers. Now if you are counting cycles i.e. you have very tight performance constraints, this could be a problem. You may be continually passing a number of references to a number of heavily used functions causing regular register spills and excessive cache misses.

Is this ever a sufficient reason? Yes it can be though in my experience it's quite rare. Interestingly, the most common case I have seen is in game engines where large singletons and tight performance characteristics are endemic.

Should you use globals? Well, that obviously depends on the detail. In most cases I would recommend not to. Even if you do have a need for a global, don't treat it as one except where necessary. In other words, pass it to the functions that use it as an argument except where the performance requirements demand otherwise. Then document the hell out of it.

As with many things in the software world, there are very sensible rules of thumb for singletons and globals but a good engineer knows when to bend them.

| improve this answer | |
  • Would this be an adequate method of refactoring? : The two solutions I created are engine-anostic. I create a IView interface and the engine envorionment implements that. This will also act as the singleton that manages all I mentioned in my OP. It will act as the main add/remove point for objects where it is unclear if they are currently in the vicinity of the player and thus need a check if they should be instantiated(in addition to adding them to the db). AI behavior that takes place further away is handled straight between the AI solution and the DB solution in its own threadpool. – user3488765 Dec 12 '16 at 7:06
  • Furthermore: I believe the optimal solution would be to implement a delegateEvent in the grid itself which passes changes to the engine. However that didn't work that great, as the engine only allows certain actions to occur in it's own thread and changing the context of the invoker to the engine thread caused micro freezes – user3488765 Dec 12 '16 at 7:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.