Notifications
Clear all

[Closed] fileIn function scope

Hi!

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:

functions.ms

function doSomething = 
  (
        print "-- I do something 
"
  )

ui.ms

filein functions.ms
  
  function create_ui =
  (
        rollout funcrollout "Functions" (
 	     button doButton "do something"
 	     on doButton pressed doSomething()
    )
  )
  
  create_ui()

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?

13 Replies

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


  global doSomething
  
  filein functions.ms
  
  function create_ui =
  (
    rollout funcrollout "Functions"
    (
      button doButton "do something"
      on doButton pressed doSomething()
    )
  )
    
    create_ui()
  

Best regards,
Daniel

Thanks for answering on a sunday, Daniel

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?

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


global someFunctions
 struct someFunctions
 (
   fn fn1 arg1 =
   (
   ),
   fn fn2 arg1 =
    (
    ),
   fn fn3 arg1 =
    (
    )
 )
 someFunctions = someFunctions()
 

the file using the functions


 global someFunctions
 
 filein "filewithstruct.ms"
 
 someFunctions.fn1 "hello"
 someFunctions.fn2 "hello2"
 

best regards,
Daniel

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

Try this

filein [b]"functions.ms"[/b] [i]--the functions.ms MUST be in quotes[/i]
   
   function create_ui =
   (
 		rollout funcrollout "Functions" (
 		  button doButton "do something"
 		  on doButton pressed [b]do[/b] doSomething() [i]--was missing the do causing an error[/i]
 	)
 	[b]createDialog funcrollout[/b][i]--create a dialog to see the button[/i]
   )
   
   create_ui()

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.

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:

  function create_ui =
    (
  		rollout funcrollout "Functions" (
  		  button doButton "do something"
  		  on doButton pressed do ::doSomething2()
  	)
  	createDialog funcrollout
    )
    
    create_ui()
    
    filein "functions2.ms"

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?

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

1 Reply
(@bobo)
Joined: 11 months ago

Posts: 0

Would it hurt trying it out?


 (
 	global myStruct
 	struct myStructDef 
 	(
 		myVar = 42,
 		fn myFunction = (format "I am printing % from inside the Struct!
" myVar)
 	)
 	myStruct = myStructDef()
 	OK
 )
 
 
 (
 	local myStruct = 123
 	format "Local myStruct is %
" myStruct
 	format "Global myStruct is %
" ::myStruct
 	format "MyVar in Global Struct is %
" ::myStruct.myVar
 	::myStruct.myFunction()
 	try(myStruct.myFunction())catch(format "Cannot call myFunction using local myStruct!
")
 	OK
 )

Output is

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…

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

Makes me wonder what other low hanging fruit is rotting away in there. It sure keeps things interesting!

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 "% % 
" 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

::
V = "GlbV"
(local V = "LocV"; format "% %
" V ::  V)

Now how’s that for ugly?

…and upon futher review…

fn AssertNotEqual a b =
(
	format "% != % is %
" a b  (a!=b) 
)

-- This works
global P = "GlbP"
fn checkP = 
(
	(
		local P = "LocP"
		AssertNotEqual P ::P
	)
)
checkP()

-- This works too
global Q = "GlbQ"
fn checkQ = 
(
	local Q = "fnQ"
	AssertNotEqual Q ::Q
	(
		local Q = "LocQ"
		AssertNotEqual Q ::Q
	)
)
checkQ()

-- Here the intermediate R declared in default (function?) scope causes :: to fail to resolved to global scope
global R = "GlbR"
fn checkR = 
(
	R = "fnR"
	AssertNotEqual R ::R
	(
		local R = "LocR"
		AssertNotEqual R ::R
	)
)
checkR()

This generates the following output:

AssertNotEqual()
“GlbP”
checkP()
LocP != GlbP is true
OK
“GlbQ”
checkQ()
fnQ != GlbQ is true
LocQ != GlbQ is true
OK
“GlbR”
checkR()
fnR != fnR is false
LocR != LocR is false
OK

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?

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.

Page 1 / 2