Skip to main content
GameDev.net gamedev.net
🔒 Locked

Global Variables

Started by RidiculousName Nov 22, 2018 at 3:41 AM 11 replies 3.3k views
Original Post
RidiculousName
RidiculousName

I'm a bit confused by what would be the best way to handle this. I have always been taught that global variables are a big no-no except when absolutely necessary and only as a constant. So, if you have a game with tons of objects that all need to be kept track of, and have to be shared between multiple files, what do you do? Do you just have to make a few data-structures and use them as globals regardless, or is there a better way?

Rutin
Rutin
29 minutes ago, RidiculousName said:

I'm a bit confused by what would be the best way to handle this. I have always been taught that global variables are a big no-no except when absolutely necessary and only as a constant. So, if you have a game with tons of objects that all need to be kept track of, and have to be shared between multiple files, what do you do? Do you just have to make a few data-structures and use them as globals regardless, or is there a better way?

I always make classes in their own files and include them into projects as needed. If you need to share data then you would either create a manager class for whatever operations you need, or just pass the objects by reference as needed. My answer is based on C++ of course. ;)

Programmer and 3D Artist
Gnollrunner
Gnollrunner
1 hour ago, RidiculousName said:

I'm a bit confused by what would be the best way to handle this. I have always been taught that global variables are a big no-no except when absolutely necessary and only as a constant. So, if you have a game with tons of objects that all need to be kept track of, and have to be shared between multiple files, what do you do? Do you just have to make a few data-structures and use them as globals regardless, or is there a better way?

Well you have a few options:

1) Make 'em globals and ignore what everyone thinks

2) Use a singleton (and ignore what everyone thinks)

4) Make a special class to hold them (kind of like a singleton, but it doesn't piss people off so much) . But then you have to figure out if the class object itself will be global or you're going to pass around it's address.

4) Put them in a namespace. This is kind of like putting them in a class but you don't have the option of passing it's address

5) Make them static and put them in a global class object. Pretty much the same as the namespace option (but you might piss people off again)

6) This one you have to think about a bit........Sometimes you will find that one of your global variables is really kind of a main object and you can add the other variables into that one as members.

I usually try to figure out which option makes the most people angry and go that direction ?

Nypyren
Nypyren
3 hours ago, RidiculousName said:

So, if you have a game with tons of objects that all need to be kept track of, and have to be shared between multiple files

Pass references/pointers to the list of objects around to the functions that need access to them. For classes which need a long term reference, store a reference in a member. For short lived references, pass as a parameter and use as a local.

This is typically easier to deal with in garbage collecting languages because there are fewer ways the concept of ownership matters.

TheCyberFlash
TheCyberFlash
11 hours ago, RidiculousName said:

I have always been taught that global variables are a big no-no except when absolutely necessary

I think I'm doing something wrong here ? I sometimes use globals, I haven't made anything 'massive' so I've not seen any issues. I would have thought in the case of needing to keep track of data between loads of objects a global would be great for this? What's the downside? :/

9 hours ago, Gnollrunner said:

I usually try to figure out which option makes the most people angry and go that direction

I like your style ?

Oberon_Command
Oberon_Command
11 hours ago, RidiculousName said:

So, if you have a game with tons of objects that all need to be kept track of, and have to be shared between multiple files, what do you do?

  1. Try to minimize those dependencies as much as possible, right down to the function level. Does your class need a thing you want global? Try to make it so that only a few of its functions do. That way you can pass the "global" in as a function argument. If class A has functions foo and bar, but only bar needs a reference to class B, don't make class A store a reference to class B!
  2. Try to keep your function call hierarchies as shallow as practical. Instead of A calling B that calls C, try to rework your code so that A, B, and C are called from the same place, in sequence. This way, if C has a dependency that A and B do not, you don't have to pass that dependency to A and B.
  3. Have a section of your code that exists just to glue things together, and put your global objects there. Your main event loop or some kind of "app" class is the place I would typically put this. You would invoke your actual business logic from the glue code, without the business logic knowing more than it absolutely needs to about the glue code.
RidiculousName
RidiculousName
13 hours ago, Gnollrunner said:

Well you have a few options:

1) Make 'em globals and ignore what everyone thinks

2) Use a singleton (and ignore what everyone thinks)

4) Make a special class to hold them (kind of like a singleton, but it doesn't piss people off so much) . But then you have to figure out if the class object itself will be global or you're going to pass around it's address.

4) Put them in a namespace. This is kind of like putting them in a class but you don't have the option of passing it's address

5) Make them static and put them in a global class object. Pretty much the same as the namespace option (but you might piss people off again)

6) This one you have to think about a bit........Sometimes you will find that one of your global variables is really kind of a main object and you can add the other variables into that one as members.

I usually try to figure out which option makes the most people angry and go that direction ?

Thanks! I think I'll try putting them in a special class. I am not an experienced enough programmer to know what a singleton or a namespace is exactly. Out of curiosity, is all of this possible in Java?

frob
frob

Use the (sometimes intimidating-sounding) pattern of dependency injection when possible. That simply means having a parameter or member that refers to the other system, which you can replace with the main system. Remember and follow SOLID principles.

There are some systems that are widely used or cumbersome to pass around. In those situations dependency injection can be a pain. A common pattern is having a well-known instance, which is far less nasty than general globals, static objects, or Singletons, introducing a visible coupling point with established dependencies.

You create a single global object that is nothing more than empty container for those systems. Those systems are created or destroyed at well-established times, and are available for the duration of the main program running. This lets you avoid problems like static initialization time that happens before main is run, unspecified initialization order that comes with global variables or lazy-constructed static instances, and allows multiple instances of objects that is anti-Singleton pattern.

You still need to be careful about hidden couplings between systems, since hidden dependencies and values that change without access control can still cause nightmares. Even so, most of the worst problems can be avoided through a bit of discipline.

All of this is perfectly compatible with Java. Have your container as a member, and create the new instance when Main starts.

Gnollrunner
Gnollrunner
19 hours ago, RidiculousName said:

Thanks! I think I'll try putting them in a special class. I am not an experienced enough programmer to know what a singleton or a namespace is exactly. Out of curiosity, is all of this possible in Java?

Ahh well Java the Hutt...... It's not my forte but....... I believe you have statics (members that are per class instead of per instance) and you of course have classes. If I remember everything has to be in a class in Java anyway so you don't really have globals (by design), but you can cheat by making a public static in a class and that should more or less do the same thing.

Also you should be able to build a singleton which is nothing more than a class that only allows one instance of itself to be created. So for instance, in the constructor you could set some flag which is a static member of the class and check to see if it's already been created. Typically you will also have some static function called "Instance", "GetInstance" or some such in your class that returns itself. The first time it's called you can create and/or initialize you class. On subsequent calls you simply return the existing class.

Alternatively depending on what you are doing you might just create the singleton instance on startup so you don't have to do any run-time checks, but..... you have to be careful with that because if you are using multiple singletons the order of initialization might be import. This is kind of true with gobbalish stuff in general. The advantage of run-time initialization is you mostly don't get in trouble with the order because you are initializing explicitly through "GetInstance".

Most of the time you don't really need a singleton. It's often just a means of protecting you from yourself. However it does have the beneficial side effect of enraging some purists, so it might be worth considering.

frob
frob

I have never seen a case where a Singleton was necessary, or was the best solution. Not one in nearly three decades. Singletons have a long list of problems, well documented elsewhere, from initialiation to destruction, from promiscuitiy and hidden dependencies to the brittleness of being forever bound to an instance. An alternative may take a little more effort, but they prevent an enormous number of headaches.

A well-known instance as described above can help with most of those. You need to follow patterns to know when they are initialied, when they are valid or invalid, when and how instances may be swapped out, but they can work well enough. Common examples of these are the well-known instances of the main console input/output streams, debugger connections, or loggers. However, they are not Singletons as you can have additional console streams, can have additional or alternate debugger connections, and can have multiple independent loggers.

You can use a well-known instance to reproduce many problems Singletons can have, like shared mutable state, but thats a different problem. You can misuse or abuse just about any pattern or practice. Study them so you can learn the dangers, then be responsible and disciplined to use them properly.

lawnjelly
lawnjelly
On 11/22/2018 at 5:49 AM, Gnollrunner said:

I usually try to figure out which option makes the most people angry and go that direction ?

It does remind me to a certain extent of a religious war. When I first started programming I used globals. When I started programming seriously I stopped using them, because they were 'mmm bad, okay'. Then someone older and wiser pointed out that no, in fact in some circumstances they make perfect sense, and may be a really good way of solving a problem, and now I tend to use them again (in somewhat convoluted fashion).

My view is I will use what I regard as the best tool for the job in hand. For instance making a couple of small games in unity and godot, and not knowing their languages very well, globals come to the rescue and gets the game finished quickly.

If I'm writing a document view app which can handle several documents open at a time, I'll make greater use of things like contexts, and objects to be operated on passed by reference (although you can still use a somewhat global mechanism within document / view by relying on the fact you can only have one current document at a time).

Games in particular are often crying out for some kind of global access, more so than many other apps, which is why purists have more difficulty justifying not using it. For instance, you want to play a sound, are you going to pass around a sound object / context through every function just in case you want to play a sound, or simply use a global function?

To me there are a lot of 'it depends' involved in how you access things. Are you working in a team (where people are more likely to screw things up when given global access), are several threads likely to be trying to change globals at the same time, how easy is it to keep track of dependencies etc etc. Another issue which often needs explicit attention is order of construction / destruction, especially in c++.

There are of course lots of ways of dressing up global access to make it more palatable. Personally I won't use singletons (unless forced to by a language that does not properly support globals), because I regard singletons as a 'solution in search of a problem', and I've seen some really bad implementations, and they are often used by people trying to convince themselves they are not using globals.

On the other side of the coin you do get people who will go to the ends of the earth to avoid using any sort of global access, and end up often using hugely verbose chains of function calls passing around 'stuff', which can make minor changes to one of these parameters a major undertaking, and often are in search of engineering perfection but never finish their projects. However I will say that the bigger the project / team there can be a bigger payoff from being really careful with access and the extra housekeeping quickly becomes worth it.

Gnollrunner
Gnollrunner
16 minutes ago, lawnjelly said:

My view is I will use what I regard as the best tool for the job in hand.

Yup. I think that's a fair statement. Everyone should use what they think is best for the given job. I do on occasion use singletons in C++ because they sometimes solve the initialization sequence problem. The down side is, if you use them that way you typically need an if every time you get your instance. The problem is in C++ the order of global initialization between different translation units is undefined so if you need to start up various systems in a certain order you have to do something. There is always the hack of putting all the relevant globals in one file. I've used that too. I personalty think if there is some fairly commonly used thing that someone is telling you "never use no matter what!", it might not be the best advice. I even use gotos from time to time.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.