Sunday, September 27, 2009

Lost Gems in MAXScript - Forcing Global Scope Access

A thread on CGTalk just made me realize that there is a hidden gem in the MAXScript syntax barely anyone knows about, let alone uses. Since it is in part my fault that people don't suspect it is there and because the next update of the MAXScript documentation is quite a few months away, I feel it is a good idea to mention it here.

If you search for "Global" in the help, the corresponding topic will only have rank 22, and the page that links to it is rank 8, but I don't expect anyone to actually read it. I promise I will reorganize these topics next time around, at least the ones related to Scope of Variables, because this is the biggest source of errors and misunderstandings in the programming practice.

Some background info first:

MAXScript has a couple of peculiarities which make coding more "relaxed" (in that the rules are more relaxed for non-programmers), but as a side effect cause a lot of problems in certain situations:

*First of all, variables do not need to be declared before being used. You can use a variable name even if it was never declared or defined, and it will have the value of 'undefined'.

*Second, variables do not require an explicit scope declaration. If a variable is used in the top level context (Listener, or a script without any parentheses), it will be automatically assumed global. If a variable is used in a context below the global (inside parentheses), it is assumed local. This is unless the developer explicitly declared it as either type. For many years, I skipped the explicit scope specification because I knew what I was doing. In the last two years or so, I started forcing myself to declare each variable as explicitly global or local to make it clearer to others - if you read the source of the Krakatoa GUI, you will notice.

Now the trouble from these behaviors is this: If you have a script calling a function or using a variable which was supposed to be defined in the global scope by some other script file, if that other script happened not to be evaluated yet, your call would cause the function to create an undefined local variable instead at evaluation time. Even if the script defining the actual function would get evaluated later, the function call would still be looking at the local variable and seeing undefined, causing an error at run time like "Call needs function or class, got undefined".

Everybody with a little MAXScript knowledge knows that the solution is to pre-declare the global variables in both scripts. This way, when the script calling the function is evaluated, the call will not create a new local variable if the function does not exist yet, but will check the local and global scopes for that name, find it in the global scope and set the pointer at that memory address. When the actual function definition is evaluated, it will also find the existing global variable and will replace the undefined value with the actual function, making the first script operational and avoiding the error.

So here is the little hidden gem most people don't know about - the double-colon :: syntax.

In the topic "Specifying Global Variables As Global Using ::" linked from "MAXScript Language Improvements in 3ds Max 8", it is demonstrated that you can prefix any variable with :: and this will force it to look ONLY in the Global Scope, completely ignoring any local scopes!

In other words, whenever you intend to call a global function or access a global variable but you are not sure whether it is already defined at the moment of script evaluation, instead of pre-declaring the variable as global in the script to ensure it is "visible" to the caller, you can simply prefix it with ::!

You can see some examples I posted in this CGTalk thread:
http://forums.cgsociety.org/showthread.php?f=98&t=810878 

You might say: But Bobo, Global Variables are EVIL! Why would you use them?
The answer is that they have their positive role when defining libraries of functions, for example a single struct definition with many functions to be accessible by many other scripts. Such libraries are normally stored in an .MS file saved in the Stdplugs\Stdscripts folder or subfolders thereof. Since that folder is the first location to be auto-loaded by MAXScript at startup, these structs and functions would be visible to any other scripts launched later.

Unfortunately, if two scripts are placed in the same folder and the one defines global "library" functions the other script should access, ensuring the loading order of these scripts become tricky and usually involves fileIn() or include() calls from another script, making things quite complicated (this is a whole other topic to write about). Thus, using :: makes the problem go away and lets you call a function or access a struct you know exists or WILL exist in global scope regardless of the script files loading order!


I believe the "Specifying Global Variables As Global Using ::" topic should be merged with the "Scope of Variables" topic or at least extensively linked from all relevant topics. I apologize that this is not the case yet and hope you will find this little known feature useful!

Happy coding!

5 comments:

  1. OMG! This is almost as cool as finding Alt-N for calculator!

    ReplyDelete
  2. Now you tell us! :D Seriously though any base level info is great! Cool blog look forward to more:)

    ReplyDelete
  3. That's exactly what I needed to know. Thank You So Much !!!

    ReplyDelete
  4. I can see this being -very- useful...

    ReplyDelete