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?

user3488765
  • 119
  • 2
  • 1
    Possible duplicate of [So Singletons are bad, then what?](http://softwareengineering.stackexchange.com/questions/40373/so-singletons-are-bad-then-what) – gnat Dec 09 '16 at 09:31

2 Answers2

4

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.

David Arno
  • 38,972
  • 9
  • 88
  • 121
  • I get your point, but from a logical perspective, I can't wrap my mind around every Unit storing a reference to the world(DB) it exists in. If I come upon a class that has a reference to something, I automatically assume that thing can be different for every instance of that class, which should never be the case here. Isn't this conveyed far better by a static class that every unit accesses if it want't information about it's world? – user3488765 Dec 09 '16 at 09:43
  • 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 09 '16 at 09:57
  • @user3488765 As David said, it's all about testing... Also don't you think there is an abstraction problem if **everything** need to know about the "world" ? – Spotted Dec 09 '16 at 12:10
  • 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 06:02
  • @DavidArno There are plenty of reasons besides testing why this is desirable. The reason you gave also applies for refactoring to a situation where you actually do want multiple "worlds", e.g. to a server that hosts multiple games or a screen-in-screen replay. – Derek Elkins left SE Dec 11 '16 at 22:11
  • @user3488765 An option besides storing a reference to the world in each unit, would be to pass the world in to each "update" method. In some cases, this may cause you to realize that your "units" are actually just pure functions. However, they're probably functions of some local state and the world. Nevertheless, this starts to lead toward [a different architecture that's common in game programming](http://gameprogrammingpatterns.com/data-locality.html), usually motivated by performance. If you follow this thread long enough, you might come to the conclusion: Game Engine = DBMS. – Derek Elkins left SE Dec 11 '16 at 23:25
  • 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 02:39
  • @immibis comments like yours are great! I guess I assumed it for the same reason M Perrson did? Because it's what you'd naturally think of. But are we talking about subinstancing parts of the world? Because I don't quite understand how one unit can exist in two worlds at the same time – user3488765 Dec 15 '16 at 07:14
  • 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
  • @immibis isn't the usual way to do that to just run multiple seperate serverinstances on a machine? Each instance can have its own sigleton – user3488765 Dec 15 '16 at 12:02
  • @user3488765 That's *one* way to do it. It might or might not be the best way. But see the Minecraft example. Minecraft now has multiple worlds as part of one game server. Splitting it into one-world-per-server would be even more difficult than fixing the server to handle multiple worlds. – user253751 Dec 15 '16 at 21:09
-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.

Alex
  • 3,882
  • 1
  • 15
  • 16
  • 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 07: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 07:13