I have seen this discussed before, and I have read through a couple of threads about it. But I still haven't quite got a proper grasp on it, so I hope you don't mind a couple of questions more on this subject...

So, I am trying to structure my scripts better, by separating the UI and the main functions into separate files. Here's a simplified example that show my way of doing it:

Now, as a couple of others write, I get an error because the rollout doesn't see the function. Running ui.ms one more time fixes that, but of course this is an embarrasing problem if I want to release my scripts!

What is the recommended way of solving this? I have tried declaring empty functions inside ui.ms, but that's seems to be cumbersome to update. Is there a better way?

dgsantana

09-27-2009, 01:26 PM

Hi SuperRune,
The easiest one it do define a global before the fileIn something like:

I suspected I had to do something like that. In my opinion that can get a bit cumbersome to update, especially since my fileIn functions file can contain a lot of functions, large and small. Is there any solution where I don't have to do a mass declaration?

dgsantana

09-27-2009, 06:05 PM

Well the only way i can remember is to use a include instead of a filein. Another may is to put all your functions in a struct (the way i do most of my scripts) and only declare the struct as a global, something like this.
One file with the struct

I have seen this discussed before, and I have read through a couple of threads about it. But I still haven't quite got a proper grasp on it, so I hope you don't mind a couple of questions more on this subject...

So, I am trying to structure my scripts better, by separating the UI and the main functions into separate files. Here's a simplified example that show my way of doing it:

Now, as a couple of others write, I get an error because the rollout doesn't see the function. Running ui.ms one more time fixes that, but of course this is an embarrasing problem if I want to release my scripts!

What is the recommended way of solving this? I have tried declaring empty functions inside ui.ms, but that's seems to be cumbersome to update. Is there a better way?

Actually your code is completely wrong but it would work if you would fix the couple of syntax errors in it.

When I run this it works because the filein actually loads the functions.ms IN GLOBAL SCOPE and predefines the function doSomething().
When I press the button in the dialog, it prints correctly -- I did something .

So your approach is right, but if you had the same typos in your test, you would have thought that it wasn't.

Bobo

09-27-2009, 07:11 PM

Oh, and another thing that some people might not realize: You can force your code to look in the global scope, removing the need for the fileIn(), or allowing it to be called later.

For example, if you change the name of the function to doSomething2 and save in the file "functions2.ms" and then run the following UI script:

you would get it working because the :: in front of doSomething2() causes it to look in the global scope ONLY. It does not find a pre-defined function there, so it creates a new global variable with that name and the value undefined.

Then the fileIn "functions2.ms" comes and loads the definition. It gets evaluated in global scope, finds the existing undefined global variable with the same name and writes into it, making the button's handler calling a now valid function.

If you did not call fileIn but let Max load the "functions2.ms" via any form of startup scripts loading, this would still ensure that after Max has booted completely, your code would work regardless of the order the two files were loaded in. If the functions2.ms was loaded first, it would work like in the previous example. If it would be loaded later, it would still work.

In other words, the :: prevents the call to the function from creating a local variable IF the function name is not defined YET, but it assumes that it will be defined at some point in global scope before the function call is to be performed.

Cool eh?

dgsantana

09-27-2009, 11:27 PM

That is really a nice trick :). We are always learning. I will use it for sure.

P.S.: Does it also work with struct functions and fields/properties?

Best regards,
Daniel

Bobo

09-28-2009, 12:52 AM

That is really a nice trick :). We are always learning. I will use it for sure.

OK
Local myStruct is 123
Global myStruct is (myStructDef myVar:42)
MyVar in Global Struct is 42
I am printing 42 from inside the Struct!
Cannot call myFunction using local myStruct!
OK

As you can see, I have the same name, myStruct, defined as global containing an actual struct instance, and again local, containing a simple integer.

I can successfully jump over the local myStruct by prefixing with :: and access the property or the function in the global struct. When not using ::, the local myStruct will become visible, the first time printing correctly its value of 123, the second time causing the try()catch() attempt to call a function in the local myStruct to catch the error since there is no struct stored locally, just globally...

biddle

09-28-2009, 06:33 AM

This the sort of thing that makes me grind my teeth.

Knowing that the C++ technique to reference a global hidden by a class/namespace var is to explicitly reference the global namespace (aka 'the empty string') MIGHT have lead me to try what would otherwise have been meaningless syntactic sugar in maxscript, but wow.

I did a survey of the docs and I couldn't find this mentioned. You can't search for "::" as it's not a valid searchable term according to the help system so it's not something I would expect to have stumbled across (otherwise I figure I would have by now :)

This is getting off topic, but once or twice I've been asked to debug code written by a conscientous programmer who worked hard to follow the rules, though they were total newbs to maxscript: huge structs & class-like code-only rollouts in separate files with locals & globals everywhere.

(in other words, me, revisting my own legacy libraries...)

Sadly people in these situations managed to hide global things with locals of the same name and solving the problem usually involved tracking down the order of declaration and then execution.

Something that the "::" syntax might have avoided...

(local framerate = "you lose"; format "% % \n" framerate ::framerate)

So Bobo, you have definitely given me something to chew on.

I think from now on I'm going consider using "::" wherever I'd have typed the word "global" and get in the habit of prefixing with it wherever I'm expecting to REFERENCE something global. Seems like a best practice.

...a word of caution though: a little playing around indicates that the parser allows whitespace between the "::" and the name, like this

I must admit I didn't expect the declaration of R the outside of the block containing the
'local' R, but within the function to cause the local to trump the '::' global

Perhaps I'm misunderstanding something?

SuperRune

09-28-2009, 11:08 AM

Great to see so much information appearing in this thread. Thanks for the answers. I will check out structs, that's something I have yet to learn.

Bobo - you will have to excuse the typos. It was meant as a rough example, and not for execution :) I still find myself having to execute the ui part twice on my larger scripts here at home.

Bobo

09-28-2009, 02:50 PM

I must admit I didn't expect the declaration of R the outside of the block containing the
'local' R, but within the function to cause the local to trump the '::' global

Perhaps I'm misunderstanding something?

You are misunderstanding something.
*First of all, not every pair of parentheses opens a new scope. So both your "local" variables are in the same scope. Please read the sub-topic "Implicit Variable Declaration" in "Scope of Variables". Only top level parentheses and bodies of functions, for loops, utility, rollout, rcmenu, macroScript and tool definitions, bodies of event handlers and when constructs create a new scope.

*Second, when you don't declare a variable in the local scope, it is assumed to be local only if there is no other variable in any parent scopes, including the global scope.

*Third (and this is the surprise), when a global variable is found and you declare a variable implicitly in a lower scope, it looks like a local variable is created in the scope that simply points at the same memory address as the global one, but it is still technically a local variable. (I have to check with the developers of MAXScript since this is not documented anywhere AFAIK).

So your code does this:

global R = "GlbR" --declare and define a global variable
fn checkR = --opens a new local scope below the global scope
(
R = "fnR" --implicit declaration, goes up looking for the same name,
--finds R in global scope, "local" R's pointer set at the global's memory
--so global R's memory is overwritten with the value "fnR"
AssertNotEqual R ::R --this shows correctly that local and global R share memory
(--does NOT create a new scope!
local R = "LocR" --this is in the same scope as the R above, so it overwrites both
--the local R and the global R since they point at the same memory already.
AssertNotEqual R ::R --it shows correctly the local R and global R share memory
)--does nothing
)--closes the only local scope
checkR()

So adding explicit local to the first declaration fixes this, and the second local R just overwrites the same variable since both are in the same scope:
global R = "GlbR"
fn checkR =
(
local R = "fnR"
AssertNotEqual R ::R
(
local R = "LocR"
AssertNotEqual R ::R
)
)
checkR()

One more reason to always declare local variables explicitly!

biddle

09-28-2009, 04:46 PM

Thanks for the clarification Bobo.

I admit I worked up those examples by wrapping code that started as top level blocks in the listener inside of functions then culling them down to just what I believed I needed. When I really think about it, it makes sense not to require a new scope for implicit variables in an interpreted language. Seems that every day I need to remind myself "this is maxscript, not perl, not c++, not c#, etc)

Given your excellent feedback, here is a clearer (at least to me) example of what can happen, and why we should always use strict scoping: