Register to post in forums, or Log in to your existing account
 

Play RetroMUD
Post new topic  Reply to topic     Home » Forums » CMUD Beta Forum Goto page 1, 2  Next
JQuilici
Adept


Joined: 21 Sep 2005
Posts: 250
Location: Austin, TX

PostPosted: Tue Nov 06, 2007 4:39 pm   

Questions on scoping and the current class
 
In a thread on enabling and disabling classes, Zugg posted some info regarding scoping of names:

Zugg wrote:
Keep in mind that:

#T- Tracking/Test

is a *relative* path reference. This says to disable the "Test" class, within the "Tracking" class, within the *current* scoping class. If you do this within a trigger, then it will be relative to the current class of the window that causes the trigger. If you do this within an alias, then it will be relative to the current class of the module that contains the alias (unless the SetAsDefault option for the class containing the alias is enabled, in which case it is relative to the class containing the alias itself).

So, as you can see, the above command depends upon a lot of stuff. That's why you need to be careful with relative references. You probably want to use:

#T- /Tracking/Test

instead. This is an absolute reference within the current window/module. And for most people who just have one window, this is all you need. If your trigger or alias is running in a different window, then you might need the syntax

#T- //ModuleName/Tracking/Test

However, the *best* way to handle all of these issues is to just make sure your class names are unique. If your classname is unique, then you can just do:

#T- Test

and it will search for the "Test" class within the current scope. In this example, "Test" is probably a bad name. But if it was something more specific and unique, then it wouldn't matter what the current class was...it would search the available scope (looking in the current class first, then the current window, then finally any global or external modules) and will find the class no matter where it is.


I appreciate this explanation of the scoping rules, but I'm still having trouble with the idea of 'the current class'. It appears, from the first paragraph above, that each module/window has its own 'current class' (e.g. you refer to "the current class of the module that contains the alias"), but it's not clear to me what the current class would be under some circumstances.

So, I'm going to read between the lines, make some guesses, and ask a few questions. Gurus/Zugg please correct and enlighten me...and if there is a document somewhere that describes this in more detail, slap me and give me a pointer.
  • In the body of a simple trigger, the 'current class' will be the root level of the window in which the trigger fired, unless the #class command is used to change it. Using #class 0 will return the current class to the root level of the window in which the trigger fired. (right?)
  • In the body of an alias, the 'current class' will be the root level of the module in which the alias is defined, unless the #class command is used to change it. Using #class 0 will return the current class to the root level of the module in which the alias is defined.
  • Do macros behave like triggers or aliases? How about events? Functions? Buttons?
  • If a class is marked as 'Set as Default', then the above rules do not apply to scripts in that class (and its subclasses?). Instead, that class is current for any script within the class.
  • Does it matter how an alias is invoked? In particular, if an alias is invoked by a trigger, what is the current class?
  • Does the location on the call stack matter? (e.g. if an alias is invoked within #class Foo ... #class 0, or from a script in a Set as Default class, what happens?)
  • What is the current class for the command-line? I'd guess that it's the current class for whatever window the command is sent to...
  • What is the current class, or any other scope info, for room scripts, end-of-speedwalk triggers, and other stuff that is logically tied to the mapper window?
  • Is there a defined order in which other classes and/or global/external modules are searched to resolve a name? IOW, if I have //Module1/Foo/doThingy, //Module1/Bar/doThingy, and //Module2/Foo/doThingy, and I call doThingy...is there any way to predict which one is invoked? I recognize that this is bad programming...but it can easily happen if Module1 and Module2 are in different packages.
  • How persistent is the 'current class' for a module? If I call '#class Foo' in a script, and don't call '#class 0', does the current class remain 'Foo' for that module even after the script terminates? Does it affect all threads? Does it persist across restarts of CMUD? Does it affect other sessions that load the same package?


I recognize that none of this matters if you keep your names unique, but that will get harder and harder as published packages start to proliferate. And robust programming (for package writers) requires that we be sure that we always refer to the things we think we're referring to.

Thanks in advance for helping me understand this stuff.
_________________
Come visit Mozart Mud...and tell an imm that Aerith sent you!
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Nov 06, 2007 6:13 pm   
 
You are correct that each module and window keeps track of it's own "current class". The "current class" is set with the #CLASS command. The current module is set with the #MODULE command (and the current window with the #WINDOW command obviously).

When you switch to a module/window using #MODULE/#WINDOW, the "current class" becomes whatever the last current class within that module. For example:
Code:
#CLASS ClassInMainWindow
#MODULE NewModule
#CLASS NowInClassOfNewModule
#MODULE 0
-> now we are back in the Untitled window and the current class is still "ClassInMainWindow"
#MODULE NewModule
-> now we are back in the NewModule module and the current class is "NowInClassOfNewModule"

Using "#CLASS 0" sets you to the top level of the current module. Using "#MODULE 0" sets you to the primary window of your session (the Untitled window in this example).
Quote:
In the body of a simple trigger, the 'current class' will be the root level of the window in which the trigger fired, unless the #class command is used to change it. Using #class 0 will return the current class to the root level of the window in which the trigger fired. (right?)

Correct.
Quote:
In the body of an alias, the 'current class' will be the root level of the module in which the alias is defined, unless the #class command is used to change it. Using #class 0 will return the current class to the root level of the module in which the alias is defined.

Correct.
Quote:
Do macros behave like triggers or aliases? How about events? Functions? Buttons?

Macros execute within the current class of the window that had the keyboard focus when you pushed the macro key. Buttons execute within the current class of the window that they are displayed within. Functions behave like aliases.

Events are a bit different. Events can be raised by the system, or raised by a #RAISE command in a script. When raised by the system, the context is usually the current class of the currently focused window. When using #RAISE, the context isn't changed, so the event executes in whatever context it was called from.
Quote:
If a class is marked as 'Set as Default', then the above rules do not apply to scripts in that class (and its subclasses?). Instead, that class is current for any script within the class.

This option does not effect subclasses. Each subclass needs to have this option set too. CMUD only looks at the parent class of an object to check this flag...it doesn't recursively check the entire class tree. Otherwise you are correct.
Quote:
Does it matter how an alias is invoked? In particular, if an alias is invoked by a trigger, what is the current class?

Aliases still execute with the context of their own module/class as above. Shouldn't matter how they are invoked.
Quote:
Does the location on the call stack matter? (e.g. if an alias is invoked within #class Foo ... #class 0, or from a script in a Set as Default class, what happens?)

Again, it shouldn't matter. When an alias is executed, the context is switched to the module that contains the alias. So whatever current class is set for that module becomes the context. When the alias is finished, the context switches back to the module/class that was current before the alias was executed.
Quote:
What is the current class for the command-line? I'd guess that it's the current class for whatever window the command is sent to...

Correct.
Quote:
What is the current class, or any other scope info, for room scripts, end-of-speedwalk triggers, and other stuff that is logically tied to the mapper window?

The mapper window is tied to a particular session window, usually the primary session window (especially since the mapper doesn't really support multiple windows). So the context is the current class for the window associated with the mapper.
Quote:
Is there a defined order in which other classes and/or global/external modules are searched to resolve a name? IOW, if I have //Module1/Foo/doThingy, //Module1/Bar/doThingy, and //Module2/Foo/doThingy, and I call doThingy...is there any way to predict which one is invoked? I recognize that this is bad programming...but it can easily happen if Module1 and Module2 are in different packages.

Yep, bad idea. This is undefined. CMUD has an internal index of settings sorted by type (alias, variable, trigger, etc) and then by Name. It looks in this index starting at the first match and checks to see if the setting is "in scope" (which means it is in an accessible and enabled class, etc). It keeps going in this index until it finds the first setting with the matching name that is in scope. There is no other sorting key for this index, so there is no way to tell which one will be first in the index...depends upon exactly how that index is implemented and what sorting routine they are using.
Quote:
How persistent is the 'current class' for a module? If I call '#class Foo' in a script, and don't call '#class 0', does the current class remain 'Foo' for that module even after the script terminates? Does it affect all threads? Does it persist across restarts of CMUD? Does it affect other sessions that load the same package?

The current class is persistent. Each Module record has a "CurrentClass" field that is updated when you use #CLASS. It effects all threads. It does *not* persist across sessions. This is stored in the in-memory cache, and not in the package file. When CMUD starts, the current class of each module is set to the module itself (like doing a #CLASS 0 in each module).

Now, those were some really good questions. This can be hard and tricky stuff, so it's possible that some of my answers are wrong, but I tried to look at the source code when answering to be sure I was telling you how it currently works. If I got something wrong, or if something should work differently, feel free to post in this thread. Maybe we can use this thread to tweak anything that isn't quite right. However, keep in mind that many of these scoping rules have gone through a *lot* of thought and testing. There are lots of issues concerning zMUD compatibility to worry about here.

Once we get these questions and answers confirmed and polished, I think this would make an excellent FAQ for scoping for the documentation if I have your permission to use your questions there.
Reply with quote
JQuilici
Adept


Joined: 21 Sep 2005
Posts: 250
Location: Austin, TX

PostPosted: Tue Nov 06, 2007 7:43 pm   
 
Please understand that I'm not advocating for any changes at the moment. Just trying to understand how it actually works.

Most of these questions were popping into my head as I was trying to design a couple of packages for eventual upload to the library, and I knew in advance that I couldn't rely on just making unique names for everything (because the end user can define names and load other packages over whose names I have no control). I kept knocking my head on scoping questions, or finding behavior that seemed odd, because I didn't understand the underlying semantics.

Follow-up questions:
  • There is an explicit syntax for referring to a setting within a specified module and class: //Module/Class/Subclass/Setting. Is there an explicit syntax for referring to a setting within a specified PACKAGE, module, and class? (If so, I can write a package that will never 'litter' other namespaces by mistake, even if some of its settings get deleted unexpectedly, or module names conflict in simultaneously-loaded packages)
  • Can I rely on names within my package to eclipse names outside my package? As a specific example, if I have an alias 'foo' in a local module, and I call it from an alias in a global module of the same package, is there any chance that some 'foo' in another package might get called by mistake? (Note that this problem arises even if I call it as //privateModule/foo, it's just less likely to conflict)
  • Are '#var myClass/myVar foo' and '#var ./myClass/myVar foo' equivalent? (Namely, changing 'the myVar variable in the myClass subclass of whatever class is current in this module')

I may put some test cases together to verify the behavior you've specified above. And I will be happy to tackle a FAQ on this subject, on a schedule to be determined by various RL activities. Wink However, you've done most of the heavy lifting in that first response.
_________________
Come visit Mozart Mud...and tell an imm that Aerith sent you!
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Nov 06, 2007 9:08 pm   
 
Quote:
There is an explicit syntax for referring to a setting within a specified module and class: //Module/Class/Subclass/Setting. Is there an explicit syntax for referring to a setting within a specified PACKAGE, module, and class? (If so, I can write a package that will never 'litter' other namespaces by mistake, even if some of its settings get deleted unexpectedly, or module names conflict in simultaneously-loaded packages)

No. This is done on purpose to prevent packages from depending upon or referencing other packages. Packages are supposed to be standalone. Honestly, it's trivial to name your modules and/or classes in a completely unique way for your package, so you should never need this. Having this syntax would just encourage more cross-package dependencies, which I think is a bad idea. If you *must* communicate with another package, it's easy enough to just provide unique module names.
Quote:
Can I rely on names within my package to eclipse names outside my package? As a specific example, if I have an alias 'foo' in a local module, and I call it from an alias in a global module of the same package, is there any chance that some 'foo' in another package might get called by mistake? (Note that this problem arises even if I call it as //privateModule/foo, it's just less likely to conflict)

CMUD will *always* look within the current package first before trying any other packages. As long as the variable already exists in your package, then CMUD should always find it first. What usually causes problems is if you use #VAR to create the variable, and the context happens to be set somewhere unexpected and then your variable ends up somewhere you didn't want it.
Quote:
Are '#var myClass/myVar foo' and '#var ./myClass/myVar foo' equivalent? (Namely, changing 'the myVar variable in the myClass subclass of whatever class is current in this module')

In that specific case, yes. Both of those say "look for myClass within the current class, and then look for myVar within myClass". However, just keep in mind that '#var myVar foo' and '#var ./myVar foo' are *not* the same. The first one will look for an existing "myVar" variable anywhere in the current scope, whereas the second one will force it to look only within the current class.

I'm happy to write the actual FAQ based upon the results of this thread. What I could use help with is creating some good and simple test scripts that would demonstrate these context rules. Also, with some good simple tests, I could add them to my automated test script to ensure that context and scoping rules don't get accidentally changed/broken in future versions.
Reply with quote
JQuilici
Adept


Joined: 21 Sep 2005
Posts: 250
Location: Austin, TX

PostPosted: Tue Nov 06, 2007 9:43 pm   
 
Zugg wrote:
I'm happy to write the actual FAQ based upon the results of this thread. What I could use help with is creating some good and simple test scripts that would demonstrate these context rules. Also, with some good simple tests, I could add them to my automated test script to ensure that context and scoping rules don't get accidentally changed/broken in future versions.

In the next few days, I want to make some of these for myself, just to make sure I understand how it all works (or should work).

Do you have a stub that you'd want the test cases to fit into (for your automation system)? Specific failure events to throw? Or should I just work on something that prints useful pass/fail messages to the active window?
_________________
Come visit Mozart Mud...and tell an imm that Aerith sent you!
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Nov 06, 2007 10:29 pm   
 
In my current test script I have an alias that will execute a script or evaluate an expression and then compare it to the "correct" value and color the line red or green and count the number of failing tests.

So really, almost anything you come up with will be useful. For example, I currently have simple stuff like this:
Code:
#class 0
#var a "Top Level"
#class sub1
; referencing @a in subclass should change global variable
#var a "Test sub"
#class 0
test "Test sub" @a
; set it back
#var a "Top Level"
#class sub1
; now, ./a creates a new reference within current class
#var ./a "sub1"
#var ./b "sub1b"
#class 0
#class "sub2"
#var ./a "sub2"
#var ./b "sub2b"
#class 0
test "Top Level" @a
#class sub1
test "sub1" @a
test "sub1" @./a
test "Top Level" @/a
test "Top Level" @../a
#class 0

The "test" is my test alias. It takes two arguments. The first argument is what the result *should* be, and the second argument is the expression that is evaluated.

But really, all I need to know is the expected correct value and the expression being tested.
Reply with quote
Vijilante
SubAdmin


Joined: 18 Nov 2001
Posts: 5182

PostPosted: Tue Nov 06, 2007 10:35 pm   
 
Since I have been playing around with a lot of weird context/scope issues and since this topic may get assembled into manual document I thought it best to put my general take on just what I am thinking in terms of these things here.

My other topic about functions and context is actually misnamed. The function is contexted correctly because the #SHOWs appear in the correct window, the scope though is not right. Also Zugg mentioned that aliases may have the same behavior and I haven't actually tested with those to see if it makes sense. I just going to give a few generalities to this so everyone can get and idea what I am thinking.

First nothing happens without a window. Every script no matter how executed is attached to a window. So in that regard the CONTEXT of every script is the window that caused its execution. The context determines where outputs such as #SAY and #SHOW are displayed, #ECHO of course still goes to the focused window. Focus is a seperate issue and should not affect the context where a script executes.

Second, every script should have a SCOPE of whatever module it is in and everything that that is in scope to its execution context. To be very clear when an alias in a module is executed its scope should be everything in that module and everything that is in scope to the window that is the context the alias is being execute in. This means that CONTEXT helps determine scope.

Third is FOCUS. Focus should only be considered when an alarm in a published module is ready to fire. By published module I mean any module that is within the scope of more then 1 window. The alarm in that state has to be instanced so that there is only 1 countdown time on it, and doing #ALL "#ALARM" should display the alarm in all windows with the correct time. Every window that has that alarm in scope should be able to adjust it with #Tą, #SUSPEND, #RESUME, %alarm, etc. When it fires though it checks the focus to determine its context. Such an alarm should be the only thing that has look at focus.

One thing I haven't played around at all with is the status window, I believe there is still only 1. Since settings could be defined for it in multiple windows it probably needs someone to really throw a few curves there, especially with functions that can execute script.

A small furtherance based on these concepts is buttons and status bars in a published module. These were recently enabled to work with a window allowing us to publish a paackage that has buttons for the session window. Technically the buttons should be displayed in every window that has that setting in scope. Because the captions can execute scripts through a function or %exec we need to be clear that the executed script has a context of the window displaying that instance of the button/status bar.

Final item, barring any in scope definition of a default class when an executing script creates a setting it should be created in the window that is the execution context. This mean that an alias in a published module will create a variable in the window calling the alias if no defined default location is set. I would want to further this slightly by saying that the window's defined default location is first, second is any belonging to the module containing the alias, and finally it is first found within scope of the execution context. I am not sure that is really doable with how the Set as Default when Execution, #CLASS, and #MODULE command s are currently stored.

That is just my group of thoughts from some of the tests I have been doing. I know the last time this stuff was really discussed the package and module design was the main thrust. I think we need a clear view of context as well as scope, and because I firmly believe that context should be part of defining scope I would like to see it all put together as a single manual section.
_________________
The only good questions are the ones we have never answered before.
Search the Forums
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Wed Nov 07, 2007 3:29 am   
 
Vijilante wrote:
First nothing happens without a window. Every script no matter how executed is attached to a window.

How about an alarm that is in a module? That is executed by a time, and could be independent of all windows.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 07, 2007 6:19 am   
 
Rather than thinking about which window #SHOW displays it's output, I actually think of "context" as: "where does a variable get created". Consider the simple '#VAR varname value' command. If this is placed in a script, and the variable doesn't exist anywhere in the current scope, then where should it be created? That is determined by the "context" in my definition of context.

Thus, a trigger creates the new variable within the window that caused the trigger to fire. But an alias creates the variable within the current class of the module that contains the alias.

With this definition of context, a script without a window (like an alias within a module) still has a context, even though #SHOW might never be used in that script. In the case of a setting with no window (like an alias within a module), using #SHOW will send the output to the currently focused window (since there isn't anyplace else for it to go). The same is done for an alarm within a module that doesn't have it's own window...the result of #SHOW will go to the focused window, but a #VAR command will create the variable within the module that contains the alarm.

So, when creating test scripts, I think it is important to test *both* the creation of new variables as well as the destination of the #SHOW command.

And yes, I agree that "scope" is slightly different than "context". Scope determines the order in which CMUD looks for other setting references (like where it looks for an existing variable of a given name). There are two ways scope is used in CMUD. I call them "FindSetting" and "Setting.InScope".

"FindSetting" searches the database for a setting of a given name. First is looks in the current class of the current module (which is the same as the "context" of the current script that is running). Then it looks in the parent class, then it's parent class until it gets to the root of the current module. Then it looks in any enabled class within a Global or Local module within the same package. Then it looks within a Global or External module within any other package. The order in which it searches these other modules is not defined, but it always looks in the current package before looking in other packages.

"Setting.InScope" takes a particular setting, like a trigger, and determines if it is "InScope". This means, "is this setting visible to the current script"? For example, when a line of text is received into a particular window, CMUD loops through all triggers in priority order and asks whether each trigger is "inscope". If a trigger is "inscope" then it's pattern is tested against the line of text received. When CMUD displays the buttons in a window, it actually loops through all of the buttons in priority order and asks which buttons are "inscope" for that particular window.

So, "InScope" first looks to see if the setting itself is enabled. If so, it looks at the class containing the setting to see if the class is enabled. This continues to each parent until you get to the top level window/module. If everything is enabled, then we check to see if the Module/Window containing the setting is the same as the current module of the "context" (for example, the "context" is the window that received the text, or the window that is displaying the buttons). If the module is different, then we check to see if it's in the same package as the current context. If so, then it returns true if the module is local or global. If the module is in a different package, then we return true if the module is External or Global.

That probably adds more confusion, but that's more of the terminology that I tend to use.
Reply with quote
JQuilici
Adept


Joined: 21 Sep 2005
Posts: 250
Location: Austin, TX

PostPosted: Tue Nov 13, 2007 5:17 pm   
 
Ok...Since this is sort of a tracking thread for creating the automated test of scoping rules, I'll put the info here.

Summary

I've created the beginning of an automated test, using variables to probe the visibility of various modules. Basically, I create an identically-named variable (testVar) in a whole bunch of places. Each copy of the variable contains its own location (module and class names). Then, I repeatedly (a) print the value of @testVar, and (b) #unvar it, using the value to make sure I get the right one (due to the fact that #unvar isn't currently working for stuff in other modules, etc.). The result is a list of the scopes that the system is checking for @testVar, in the order that it checks 'em. Or that's the theory, anyway.

Implementation

Currently only partially implemented. To replicate my current setup:
  1. Create a new, empty session named "ScopeTest". This will have the default window named "ScopeTest" and nothing else. (Point it to whatever server you want - we'll only use it offline. It's just a pain to do all the work below in the untitled session each time you want to test)
  2. In the session, create a package "ScopeTest01" with four modules: External01, Global01, Local01, and ScopeTest01. The external/local/global modules are empty, but with the obvious options set. The ScopeTest01 module (also global) has the good stuff:
    Code:
    <?xml version="1.0" encoding="UTF-8" ?>
    <cmud>
    <module name="Global01" global="true">
      <uid>{C8C342AA-B81A-42D2-A0CF-D5279923972E}</uid>
    </module>
    <module name="Local01">
      <uid>{C50CFB83-5169-4B91-B47A-07A68559B0EB}</uid>
    </module>
    <module name="External01" external="true">
      <uid>{C24B4371-402F-4D89-ACC5-7B8DC94A5980}</uid>
    </module>
    <module name="ScopeTest01" global="true">
      <uid>{3FABEC08-3B84-4207-B6B8-CB493AB02F0B}</uid>
      <packages>ScopeTest02</packages>
      <alias name="setupVars01">
        <value>#show "Doing setupVars01"
    ; 'Current' module for this alias, whatever that is.  This may get overwritten!
    setupModuleVars01 0
    ; Module for the current window, if accessible.
    setupModuleVars01 %window
    ; Module containing definition of this alias
    setupModuleVars01 ScopeTest01
    ; Global Module in defining package
    setupModuleVars01 Global01
    ; Local Module in defining package
    setupModuleVars01 Local01
    ; External module in the other package (since it's visible here)
    setupModuleVars01 External02</value>
      </alias>
      <alias name="showScopeTest01">
        <value>#say "*** showScopeTest01 - root level"
    #while (@testVar) {
      $tv=@testVar
      $mod=%item($tv,1)
      $cl=%item($tv,2)
      #say %concat("*** showScopeTest01: ",$tv)
      #debug module $mod class $cl
      #module $mod
      #class $cl
      #unvar testVar
      #class 0
      #module 0
    }</value>
      </alias>
      <alias name="stepScopeTest01">
        <value>$tv=@testVar
    $mod=%item($tv,1)
    $cl=%item($tv,2)
    #say %concat("*** stepScopeTest01 - root level: ",$tv)
    #show module $mod class $cl
    #module $mod
    #class $cl
    #unvar testVar
    #class 0
    #module 0</value>
      </alias>
      <alias name="clearVars01">
        <value>#show "Doing clearVars01"
    clearModuleVars01 0
    clearModuleVars01 %window
    clearModuleVars01 ScopeTest01
    clearModuleVars01 Global01
    clearModuleVars01 Local01
    clearModuleVars01 External02</value>
      </alias>
      <alias name="setupModuleVars01">
        <value>; setup vars in the named module
    #module $mod
    #class 0
    #t+ ./testVar
    #var ./testVar %concat($mod,"|0")
    #t+ ./Class01
    #t+ ./Class01/testVar
    #var ./Class01/testVar %concat($mod,"|/Class01")
    #t+ ./Class01/SubClass01
    #t+ ./Class01/SubClass01/testVar
    #var ./Class01/Subclass01/testVar %concat($mod,"|/Class01/Subclass01")
    #module 0</value>
        <arglist>$mod</arglist>
      </alias>
      <alias name="clearModuleVars01">
        <value>#module $mod
    #class 0
    #unvar ./testVar
    #delclass ./Class01
    #module 0</value>
        <arglist>$mod</arglist>
      </alias>
    </module>
    </cmud>

  3. Create a second package "ScopeTest02", with modules External02, Global02, Local02, ScopeTest02, similarly flagged as external/global/local/global respectively:
    Code:
    <?xml version="1.0" encoding="UTF-8" ?>
    <cmud>
    <module name="ScopeTest02" global="true">
      <uid>{78C54AA2-835E-42B5-9220-FFF1FAAA29B1}</uid>
      <packages>ScopeTest01</packages>
    </module>
    <module name="Local02">
      <uid>{3FE38BB2-D08F-4000-ABE7-D88485EDF165}</uid>
    </module>
    <module name="Global02" global="true">
      <uid>{1F52091B-77BD-445D-8BCE-4CFE145A7457}</uid>
    </module>
    <module name="External02" external="true">
      <uid>{0AD89609-DB94-4067-B4EE-B6D878988866}</uid>
    </module>
    </cmud>

  4. Mark package ScopeTest02 as enabled for module ScopeTest01, and package ScopeTest01 as enabled for module ScopeTest02. Both packages should be enabled for window ScopeTest.
  5. Screenie of the final setup in Package Editor:



EDIT: Improved this description after I remembered how to get XML for a whole package, not just a module.

Execution

NOTE: All tests shown below are run under 2.11. So the 'current module' for all settings (aliases, etc.) which we call from the command line will be the main window (ScopeTest)

To set up the (first, partial) test, type at the command line:
Code:
setupVars01

Now, I run into my first surprise. Variables have been created as I expected in the window, and in the ScopeTest01 and Global01 modules. However, the others are strange:
  • Variables are created in the Local01 module as well. However, if I understand properly, that module should NOT be accessible to our script (which is executing in the context of the main window).
  • Variables are NOT created in the External02 module, which (again, unless I'm confused) SHOULD be accessible to the main window. Instead, the system creates a new, local External02 module in the default package, and puts the variables there.

Either of these effects could be due to the way that I am creating the variables (e.g. #module External02;#class 0;#var ./testVar "External02|0") - perhaps it would be different just doing #var //External02/testVar "External02|0". I will do more testing. In the meantime, gurus please help me understand if either of those are bugs or just my own misunderstanding.

To RUN the (first, partial) test, type:
Code:
showScopeVars01

Which, for me, in 2.11, produces:
Code:
*** showScopeTest01 - root level
*** showScopeTest01: ScopeTest|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: ScopeTest|0
Variable testVar removed.
*** showScopeTest01: External02|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: External02|/Class01
Variable testVar removed.
*** showScopeTest01: External02|0
Variable testVar removed.
*** showScopeTest01: ScopeTest|/Class01
Variable testVar removed.
*** showScopeTest01: Global01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: Global01|/Class01
Variable testVar removed.
*** showScopeTest01: Global01|0
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01
Variable testVar removed.
*** showScopeTest01: ScopeTest01|0
Variable testVar removed.

Again, note some odd features:
  • In spite of the fact that the test is executed in the root class of the main window, the test finds //ScopeTest/Class01/Subclass01/testVar first. and //ScopeTest/testVar second.
  • The test finds all the vars in //External02 before it finds //ScopeTest/Class01/testVar. (Remember that External02, in spite of the name, is a local module in the default package - see above)

It is also possible that this is not deterministic - I have other runs in my scrollback buffer where the root-level testVar is first (but the External02 vars are still ahead of some vars in the main window).

Both of the above appear to be bugs.

Finally, to clean up:
Code:
clearVars01

This SHOULD delete all the variables and classes/subclasses created by setupVars01. However, the root-level testVar in the Local01 package does NOT get deleted:



That is almost certainly a bug, somehow. I can't delete a variable I created, although I can delete the class/subclass right next to it. Either I should be able to delete the var, or I never should have been able to create it. Anyway, you will have to manually delete this variable to get back to the initial state.

Also, note that you will have to manually delete the External02 module in the default package, as well, to get back to a completely clean state.

Future Work
Clearly, this is just the beginning. I need to test some other cross-package scoping scenarios. And I need to test variable scoping rules when running in a subclass, or outside the main window. And I need to test variable scoping from triggers, macros, events, room scripts, etc. I also need to test the scoping of non-variable settings (e.g. triggers, alarms, aliases). But I wanted to get feedback on the basic METHOD before I went too far down the road.

I will be happy to create bug threads for the items I have listed above, or move this to its own thread, or whatever. I just wanted to have wiser eyes look at my method and results before I started spamming threads.
_________________
Come visit Mozart Mud...and tell an imm that Aerith sent you!
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 7:08 pm   
 
Wow, excellent work. I'm in the middle of playing with your scripts. I'll post answers to your questions as I get to them. Here is the first one:
Quote:
Variables are created in the Local01 module as well. However, if I understand properly, that module should NOT be accessible to our script (which is executing in the context of the main window).

When you explicitly use #MODULE, you are overriding the normal scoping rules. You can use #MODULE to modify any module, even a Local module. So it's normal that you were able to create the variables in Local01 using your setupVars01 alias. What "Local" means is that doing "testvar=value" from your ScopeTest window will never modify the @testvar within Local01. But again, you can always override this using #MODULE or by forcing the syntax '//Local01/testvar=value'

Edited: BTW, to anyone else who might be playing with these packages...go into the Package Properties for each package and check the ReadOnly flag after you have created the initial package files. This will prevent any of your playing around from messing with the stored packages so that each time you open the ScopeTest session you will get the initial clean setup. (although there seems to be a bug saving the ReadOnly property that I've added to the bug list)


Last edited by Zugg on Wed Nov 14, 2007 7:23 pm; edited 2 times in total
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 7:10 pm   
 
Quote:
Variables are NOT created in the External02 module, which (again, unless I'm confused) SHOULD be accessible to the main window. Instead, the system creates a new, local External02 module in the default package, and puts the variables there.

Might be something I already fixed in v2.12. In my current version of CMUD, I get the variables properly created in External02. I do not get a new External02 module in the default package.

One change in 2.12 that might effect this is that the #WINDOW command properly sets the context in 2.12, when it didn't in 2.11. Or, it could be from some other bug fixes.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 7:19 pm   
 
When running "showScopeTest01" (you have a typo in your post and called it showScopeVars01) I get the following output in v2.12:
Code:
*** showScopeTest01 - root level
*** showScopeTest01: ScopeTest|0
Variable testVar removed.
*** showScopeTest01: ScopeTest|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: ScopeTest|/Class01
Variable testVar removed.
*** showScopeTest01: External02|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: External02|/Class01
Variable testVar removed.
*** showScopeTest01: External02|0
Variable testVar removed.
*** showScopeTest01: Global01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: Global01|/Class01
Variable testVar removed.
*** showScopeTest01: Global01|0
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01
Variable testVar removed.
*** showScopeTest01: ScopeTest01|0
Variable testVar removed.

So this is looking better already. It finds the "ScopeTest|0" variable first, as it should. And it finds the "ScopeText|/Class01" stuff before the "External02" stuff (remember that External02 *is* in the other package in my testing).
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 7:23 pm   
 
Quote:
This SHOULD delete all the variables and classes/subclasses created by setupVars01. However, the root-level testVar in the Local01 package does NOT get deleted

Confirmed this in 2.12 as well. Not sure why this isn't getting deleted, but it's on the bug list to look at.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 7:25 pm   
 
Also, in 2.12 I add the following event which helps me remember how to use these tests. This event is created in the ScopeTest01 module.
Code:
<event event="onLoad" priority="100" id="10">
  <value>#SHOW Use 'setupVars01' to create variables
#SHOW Use 'showScopeTest01' to run test
#SHOW Use 'clearVars01' to remove variables
#SHOW "-----"</value>
</event>

Obviously this won't work for anyone else until 2.12 is released ;)
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 7:32 pm   
 
Hmm, something really strange is definitely happening here. I just reloaded and ran the test again and got this result:
Code:
*** showScopeTest01 - root level
*** showScopeTest01: ScopeTest01|0
Variable testVar removed.
*** showScopeTest01: Local01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: Local01|/Class01
Variable testVar removed.
*** showScopeTest01: Local01|0
Variable testVar removed.
*** showScopeTest01: Global01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: Global01|/Class01
Variable testVar removed.
*** showScopeTest01: Global01|0
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01
Variable testVar removed.
*** showScopeTest01: External02|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: External02|/Class01
Variable testVar removed.
*** showScopeTest01: External02|0
Variable testVar removed.
*** showScopeTest01: ScopeTest|0
Variable testVar removed.

Which is completely different than either previous result. So, there is obviously an obscure bug somewhere that is changing the order in which it finds the variable. And this might be the cause of all of the scoping headaches that people have been having. Obviously if it isn't consistent, then there is a real problem somewhere!
Reply with quote
JQuilici
Adept


Joined: 21 Sep 2005
Posts: 250
Location: Austin, TX

PostPosted: Wed Nov 14, 2007 8:08 pm   
 
Zugg wrote:
When you explicitly use #MODULE, you are overriding the normal scoping rules. You can use #MODULE to modify any module, even a Local module. So it's normal that you were able to create the variables in Local01 using your setupVars01 alias. What "Local" means is that doing "testvar=value" from your ScopeTest window will never modify the @testvar within Local01. But again, you can always override this using #MODULE or by forcing the syntax '//Local01/testvar=value'

Ok...once again, it's not the way that I expected it to work, so let's see if I can understand the way it DOES work.

Assume for the moment that I have not used #module to change out of the main window's context. You appear to be saying:
  1. The reference @testVar will NEVER find //Local01/testVar. IOW, //Local01 and all its contents do not appear on the 'search path' that the system uses to resolve names that are not explicitly rooted.
  2. However, @//Local01/testVar WILL find that variable. //Local01 is not 'in scope' (using your earlier definition), but it's not invisible, either. Or to put it another way, 'in scope' just means 'on the search path'.

For whatever reason, I had always assumed that a variable 'out of scope' could not be accessed, even using the //module/name syntax.

However, this means that the failure to create variables in the //External02 module is definitely a bug.
_________________
Come visit Mozart Mud...and tell an imm that Aerith sent you!
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 8:10 pm   
 
Found it! Yep, this was a serious problem. Each thread stores it's execution context (the module/class to execute within). This wasn't getting reset across shared threads taken from the thread pool. So sometimes the "showScope01" alias was getting executed from a different context (like from within ScopeTest01 instead of the regular ScopeTest window). So that explains the variable results. I've got this fixed now in 2.12.

I also added a new predefined variable called %module which returns the name of the current module. Remember that %class returns the current class (including the module). These variables can be used to save/restore the current module and/or class. Since %class includes the module name, doing #CLASS %class will also set the proper module. But I added %module so that I could easily write a debug message showing which module was actually active at the beginning of the alias, and that's how I tracked down the bug.

Still not getting the Local01 variable to delete yet, so still working on that.
Reply with quote
JQuilici
Adept


Joined: 21 Sep 2005
Posts: 250
Location: Austin, TX

PostPosted: Wed Nov 14, 2007 8:13 pm   
 
Zugg wrote:
Hmm, something really strange is definitely happening here. I just reloaded and ran the test again and got this result:
<intentionally munched>
Which is completely different than either previous result. So, there is obviously an obscure bug somewhere that is changing the order in which it finds the variable. And this might be the cause of all of the scoping headaches that people have been having. Obviously if it isn't consistent, then there is a real problem somewhere!

Yes and no. It's definitely weird (and wrong) that names outside the main window are found ahead of things inside the main window. However, anyplace where the order is undefined, I don't care if the order is consistent between runs. IOW, it doesn't matter whether it finds //Local01/Class01 ahead of //Global01 or whatever, and it needn't be consistent between runs...but it shouldn't find either one until it's exhausted //ScopeTest and all its classes.

I'll see if I can put together some larger test cases, now that I understand the correct semantics for #module (see above).

EDIT: Cross-posted. Glad you found it! It should be fairly easy to incorporate some saving/restoring of contexts into the tests, too.
_________________
Come visit Mozart Mud...and tell an imm that Aerith sent you!

Last edited by JQuilici on Wed Nov 14, 2007 8:17 pm; edited 1 time in total
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 8:15 pm   
 
Quote:
The reference @testVar will NEVER find //Local01/testVar. IOW, //Local01 and all its contents do not appear on the 'search path' that the system uses to resolve names that are not explicitly rooted.

Correct. And it's good to remember that a Window is always *local*.
Quote:
However, @//Local01/testVar WILL find that variable. //Local01 is not 'in scope' (using your earlier definition), but it's not invisible, either. Or to put it another way, 'in scope' just means 'on the search path'.

Correct. The "scope" is the search path used to find variable references that do not use the full path.

This was done because some people still wanted a way to access variables within a local module (or window) of another package or session.

Quote:
However, this means that the failure to create variables in the //External02 module is definitely a bug.

I'm not getting that problem in 2.12, so it might already be fixed.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 8:31 pm   
 
OK, found it. The problem with deleting variables was that the #UNVAR command didn't support paths. So doing '#UNVAR ./testvar' wasn't handling the ./ properly. Once I fixed the bug with the thread context, the clearVars01 wasn't deleting *any* of the top-level variables. And this was because of the ./ problem with #UNVAR. I've got that fixed now, and now your test is working great for me here.
Reply with quote
JQuilici
Adept


Joined: 21 Sep 2005
Posts: 250
Location: Austin, TX

PostPosted: Wed Nov 14, 2007 8:54 pm   
 
Zugg wrote:
The problem with deleting variables was that the #UNVAR command didn't support paths.

Glad to hear it. You might want to take a quick look at some of the other delete-ish routines, to see if the same bug exists. For instance, IIRC, in zMUD, the #delkey command didn't handle paths. (Yes, I'm bad. I ran into it, worked around it, never reported it, never thought about it again. Embarassed).

I'll try to incorporate some of those commands into this scope test, too.
_________________
Come visit Mozart Mud...and tell an imm that Aerith sent you!
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Nov 14, 2007 9:03 pm   
 
Yep, I fixed all of the #UN commands at the same time.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Fri Nov 16, 2007 12:11 am   
 
OK, after our other scoping discussions I have made some changes to v2.12. Here is the result of running the scope test scripts given above in v2.12:
Code:

*** showScopeTest01 - root level
*** showScopeTest01: ScopeTest01|0
Variable testVar removed.
*** showScopeTest01: ScopeTest|0
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: ScopeTest01|/Class01
Variable testVar removed.
*** showScopeTest01: ScopeTest|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: ScopeTest|/Class01
Variable testVar removed.
*** showScopeTest01: Local01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: Local01|/Class01
Variable testVar removed.
*** showScopeTest01: Local01|0
Variable testVar removed.
*** showScopeTest01: Global01|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: Global01|/Class01
Variable testVar removed.
*** showScopeTest01: Global01|0
Variable testVar removed.
*** showScopeTest01: External02|/Class01/Subclass01
Variable testVar removed.
*** showScopeTest01: External02|/Class01
Variable testVar removed.
*** showScopeTest01: External02|0
Variable testVar removed.


1) It uses the variable defined within the same class as the test script first (ScopeTest01|0). This is the result that we want based upon the discussion in the Simple Scope Question thread.

2) Next, it finds the variable within the current window (ScopeTest|0). This is correct because we want to look for any "instance" variable within the current window if there isn't anything defined in the package that contains the alias.

3) OK, now we have exhausted the two obvious variables. Now what? Next we would start looking in "other classes within the same module". But within *what* module? The module containing the alias? or the the current window? To be consistent with (1) and (2), CMUD now looks within the module containing the alias first. So it finds "ScopeTest01|/Class01/Subclass01" buried in a subclass of the package. Next, it finds the "ScopeTest01|/Class01" variable. Now all variables within the ScopeTest01 module have been exhausted. This is consistent with what zMUD did...looks for any variable in other enabled classes or subclasses. The order in which classes are searched is undefined. If the variable existed in "Class01" and "Class02" then you won't know which it will find first. In this case it found the variable in a subclass first, but it could have found the Class01 variable before the Subclass01. You just never know.

4) So next, CMUD will look in other classes within the current window. So it finds the "ScopeTest|/Class01/Subclass01" reference, and then the "ScopeTest|/Class01" reference. Now all matching variables anywhere in the current window have been exhausted. Again, the order in which these two are found is undefined.

5) Next we want to start searching in other modules within the "same package". Again, we first look in other modules within the same package as the module containing the alias. So we find the "Local01..." and "Global01..." references. The order of these packages is undefined. It might look in Local01 first, or it might look in Global01 first. It depends upon the sort order of the database.

6) Next CMUD will search in the other modules within the same package as the current window. In this example, there are not any other modules in the "ScopeTest" package, so nothing is found.

7) Finally, CMUD will search any other packages that are enabled. So it finally finds the variables in the External02 module of the ScopeTest02 package. The order in which these are returned is undefined. But it will return all of the variables within one package before going to the next package.

So, that completes the new searching rules. The same rules are used for searching for any setting. Read this a couple of times and take a close look at the output and then let me know if you disagree with this search order.
Reply with quote
Vijilante
SubAdmin


Joined: 18 Nov 2001
Posts: 5182

PostPosted: Fri Nov 16, 2007 12:25 am   
 
I absolutely agree with the order for searching! Next up order of creation.
_________________
The only good questions are the ones we have never answered before.
Search the Forums
Reply with quote
Display posts from previous:   
Post new topic   Reply to topic     Home » Forums » CMUD Beta Forum All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

© 2009 Zugg Software. Hosted by Wolfpaw.net