Globals

I open a script I did not write
And sigh or scream when I often find
That dreaded keyword which gives me such fright,
Perhaps I should not be so kind?
Why do they stack them a dozen high,
And why don’t they just put them in a struct,
When they are just three letters long I sigh
And think that I am really fucked.
Just put them in local scope,
Oh my god who wrote this shit
The rest is better I sincerely hope
Or I will have to refactor it.
It is of globals I write this ode,
So please keep them out of your code.

[hr][/hr]

I’m putting together a guideline for when globals are acceptable. I see them overused, especially by people not experienced or interested in code design, and I hope after reading this you’ll use them more strategically.

This is written with Max in mind but should be relevant whatever 3d package is used.

Why are globals bad?
[ol]
[li]Naming conflicts: These can be really nasty though infrequent. If you declare something a global, and someone else uses the same variable and doesn’t declare it a local (maybe in some small script or something, or they just forgot/commented it), they will be using an incorrect value, and/or so will you. This is especially a problem with people who use globals to track UI settings. You can prefix your global with a bunch of letters, but this is inconvenient to type and makes your script a big pain to read.
[/li][li]Tracking and debugging: You generally want your script to interface with another in very specific ways- you don’t want to set the same pool of globals with two scripts, usually, or use them as intermediates. This makes it very hard to track and debug code. Code design should be very obvious and explicit. If you need to store intermediary data, storing it as a bunch of globals is not ideal because it is not straightforward.
[/li][li]Readability: Globals make code hard to read. Declaring a bunch of globals, changing those globals, then using them, even in the same script, is not easy to read. Say you have ‘g_var = something.state’, and then later ‘if g_var then ()’, you need to find where you set g_var to read the code properly. And the most recent place it is set, since it may be set in various places.
[/li][li]Refactorability: If you don’t understand why refactoring code is necessary, you probably use globals too much. All the above factors influence one another, but also make refactoring a huge pain. Unnoticed dependencies may have appeared to do the global (not always directly using the global but can be consequences). Taking the global out/renaming it can cause bugs. Not being able to track or debug code means the errors you make during refactoring are more difficult to correct. Not being able to read code easily means understanding how to make a good refactor, and the rate of refactor, are both negativity affected.
[/li][/ol]

Global-addicts may not see these as especially pressing issues. But go back to global-ridden code you’ve written a few months ago that you haven’t been in since, and try to edit or add to it, or use it as a foundation for something. The vast majority of code development time is spent bugfixing and maintaining, not in the actual first writing. Saving a few minutes development time by using globals will cost you exponentially more later on. And not always your development time- often that of your coworkers or even people you don’t know who are going to use your code.

Here are scenarios when globals are required or useful:
[ol][li]execute strings: execute strings are in a global scope, with no way around it. So no matter where the ‘execute’ statement is, it will be global. Just be smart about it, and never use execute unless you really need to (which is rare).
[/li][li]Communication: You may not think of it as such, but functions are often in global scope as well, even without declaring them ‘global’ outright. It is much more common to communicate between code with functions than variables, so somehow people seem to think global functions are alright. While they are more acceptable, they should still be avoided. (The reason they are more acceptable is because functions are not modified outside of their definition, so it is a much more linear matter to see which function a statement is using, whereas a global variable can be fried, poached, or scrambled without you knowing). But you’ll need globals of some sort to communicate between two blocks of code who are children of global scope.
[/li][li]structs: You’ll still need some globals, obviously. The best way to minimize, or even eliminate, the negatives is to limit globals to structs. Using structs will help you eliminate global use almost entirely, except for the structs themselves. Think about this. A struct should describe an idea: string functions, functions for a tool, etc. These concepts are generally on a much higher level than functions or variables. For example, ‘setOptions’ or ‘facesToDelete’ are common function/variable names that can be used by many different scripts. So, to differentiate, you could say ‘skinFn_setOptions’/‘renderFn_setOptions’ and ‘g_toolA_facestoDelete’/‘g_toolB_facesToDelete’. You’ve now avoided naming conflicts but still have every other negative of globals. Instead, you could have a struct for skin/render functions, and toolA/toolB. This avoids naming conflicts (you should never have two ‘skinOps’ structs, for example). Tracking and debugging is much simpler because everything is much more centralized and organized. Readability is improved, as it uses a very standard ‘.’ which is the essence of classes in the first place and everyone understands. Plus no pointless prefices. And refactorability is innately improved because all the other problems are fixed- plus the better code design and layout implicitly lends itself to easier refactorability and maintenance.
[/li][/ol]

Here are scenarios when you should never use globals:
[ol]
[li]Storing UI settings: Never, ever, use globals to store UI settings. To remember settings through sessions, always store them in a struct. To read a UI, access the UI itself, or even better when communicating between two unique tools, communicate via data stored in structs.
[/li][li]As locals: If a variable doesn’t need to be global, never ever declare it as such. The only time it needs to be global is for communication between unrelated scopes. I see plenty of scripts with globals haphazardly declared. Very bad practice. If they are necessary, make them local, but more likely you can find a better way (it is rarely needed to declare a bunch of locals for a rollout or macro, for example).
[/li][li]Inside functions: There are very, very few instances where this rule can be broken. Think about this. If your function’s scope is not global, you should never need a global inside of that (just declare the local above the function and you can use it in the function… but be careful with this as well). So, you now have global vars being used in global functions. In which case you now have a ‘system’ of variables, functions, and scripts, in which case it should certainly NOT be in global scope. I don’t think I’ve ever found a case where globals must be used inside a function- there has always been a workaround, which ends up making much more sense than the global route.
[/li][/ol]

And as a little addendum, something mentioned above:
[ol]
[li]Pair tools/rollouts with structs: Something I’ve only started doing recently, but has made my code much simpler and straightforward. I have a tool with 5 rollouts. These rollouts refer to common variables and processes, but are all unique rollouts. One way is to use globals, this is probably most common. Don’t do this. Another potential way is to define a ‘master’ rollout, and then all rollouts refer to that. However, this is messy, and prone to initialization and persistence errors. It is also not very flexible- what if scopes or names change, or change the master?
[/li]The best way pairs structs with tools. The struct stores all the common variables and functions, and each rollout refers to the struct. What is even better is, you don’t need to call the variable in the struct when calling a function in the struct, if it uses its local variable. This is a deep topic and deserves its own article, but I hope this sparks ideas.
[/ol]
[hr][/hr]

I wrote this in two chunks so sorry for any incompleteness or discontinuity. Once I get some feedback and other suggestions I’ll put it on the Wiki. The mentioned tool/struct pairing article should be coming soon as well.

Hey Rob,

Do you happen to have a script to exemplifies the use of structs well? I’d like to stop using globals as much as I can and would like to check out some other scripts and how they are going about it.

Cheers,

Tyler

You can check out the UI Manager script:
http://tech-artists.org/hg/tao_official/tao_tools/file/2aeeb20e7b5e/uiMgr.ms

It has two structs- the first is the main struct that does most of the work. The second is the ‘interface’ struct users should call, which calls the first struct. This is an extra step of abstraction that may not be entirely necessary, but there is no way to make struct members private so I just put them in a different struct… people who want to use the script know exactly where to look (in the second struct) rather than having to scour only parts of the first struct.

I have to declare a global (uiMgr) as an instance of the second struct to use it (because the first struct self-references variables, which requires an instance… structs are not classes, unfortunately, and the definitions can’t have variables with values).

If I didn’t need to use uiMgr from any other scopes, there’s no reason uiMgr needs to be a global- I can define it as a local in a macro, rollout, or any other script, thus cutting down on yet another global.

Since I write all my rollouts now with event handlers that just point to a function (or multiple functions) in a struct, the only global I need is the struct definition, the instance is local to the rollout (or wherever). So the rollout is really bare-bones, makes it great for automated control and much more flexibility in changing the UI. I started writing a more fleshed-out article with code example on the Wiki, I need to do a Wiki overhaul this week as is- I will finish it up and post the link here when done.

Most of this stuff should be fairly known to most MaxScripters, but just in case…

I’m so paranoid that all globals must have a “g_rmd” prefix just to avoid collisions. There is a scary amount of scripts you can get from ScriptSpot etc which happily will declare strings like “fDistance” as global. Obviously none of our stuff should break from someone having scripts like that on their machine, but other “3rd party” scripts often break other “3rd party” scripts and Max refuses to start…

All of our globals need to be declared in a “RemedyInit.ms” file in stdplugs\stdscripts, so their scope REALLY is global. If you declare values global just in a script file which gets loaded from the “Plugins” folders when Max is starting up, you’re just asking for trouble.

E.g, the order in which scripts are loaded from a “plugins” folder on Vista is somewhat random (I think it’s the file creation date or something). Thus, if you have A.ms and B.ms and both of them declare “variableA” (from A.ms) and “variableB” (from B.ms), you can not use them globals from the other script, since when loading A.ms the value of variableB is “undefined” and bad things will happen since the variableB is defined only once the A.ms has finished loading and compiling…

SamiV.

[QUOTE=samivRMD;2237]There is a scary amount of scripts you can get from ScriptSpot etc which happily will declare strings like “fDistance” as global.[/quote]
I opened some code someone wrote on CGTalk, and it had a global of two letters, though I can’t remember which. TWO LETTERS. Wtf? And the kicker is, the script was originally encrypted… try debugging THAT problem, my goodness.

All of our globals need to be declared in a “RemedyInit.ms” file in stdplugs\stdscripts, so their scope REALLY is global. If you declare values global just in a script file which gets loaded from the “Plugins” folders when Max is starting up, you’re just asking for trouble.

I like to think of Max’s parsing of scripts as line-by-line with object orientation (and not compiling, per se. Max calls it ‘evaluate’ for a reason, it is just like ‘execute’ on the entire script). You can’t use anything before it is defined, and while this is obvious in normal code, it isn’t so obvious when inside of structs, or between functions, or when a script uses a global defined in another script. For example, we were using some puppetshop functions (like puppet.getPuppetNodes() ) before we loaded PS, and the script would crash because puppet wasn’t defined when Max parsed the script. You can get around it by loading in the proper order (NOT always possible), or declaring all your globals before you load any scripts (sometimes difficult, and this appears to be what you’re doing at Remedy), we use a combination of the two (load all our globals/structs first, then load PuppetShop (which has some of its own globals, mostly struct names), then define our structs since we only declared the globals in the first step, and not what they are. This is not exactly an intuitive problem- it is not something new scripters think about, and not something people coming from compiled languages have to deal with. There was another thread on this somewhere, maybe it was on CGTalk, can’t remember…