|
Zhiroc Adept
Joined: 04 Feb 2005 Posts: 246
|
Posted: Thu Aug 16, 2007 4:27 pm
Packages, and private data |
I get the basic concept behind packages except for one thing--data that a package needs to use, but not be shared.
For example, let's make a concrete example. I have for zMUD a Buddy class that simply colorizes the names of characters I interact with often. So, I use a stringlist called buddylist, and do an RE trigger based on "\b(@Buddy/buddylist)\b" to do #CW's. So, I want to make this a package to share among all the MUSHes I play.
The package part for the trigger is simple. But what about @buddylist. It shouldn't be in the package. It should be in the character's own package (or maybe, in a per-game package). The buddy package needs to read this variable for the trigger, as well as to write to it to add and delete buddies using commands (rather than using the editor directly).
In zMUD, a setting in the character would be used in preference to one in the inherited settings (which were read-only from zscript), thus allowing for shared code/private data. How is this done using packages? |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Thu Aug 16, 2007 5:07 pm |
Triggers run within the context of the window that received the text that fired the trigger. So if your trigger just accesses @buddylist normally, it will use the @buddylist from whatever window fired your trigger. Each window could have it's own @buddylist and each should work with the single trigger in your package. So I don't think you need to do anything special in this situation...just put your trigger in a Global or External module within the package.
|
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Thu Aug 16, 2007 5:09 pm |
Oh wait, I see what you are talking about...you want to have @buddylist in the trigger pattern itself. I had missed that.
That will cause a problem. The trigger pattern gets compiled, so a given trigger can only have a single pattern at any given time. I'll have to give this some more thought on how you would do it. |
|
|
|
Tech GURU
Joined: 18 Oct 2000 Posts: 2733 Location: Atlanta, USA
|
Posted: Thu Aug 16, 2007 5:15 pm |
You can still do the same thing in CMUD. You can access the variables use using the @//ModuleName/ClasssName/VariableName syntax.
Let's say your package with the colourizing script is called Buddy. Now each of you Mush session have their own default Package. Let's say for each session package you have module call Data. In it you have Variables folder containing buddy list variable 'BuddyList' and at the root level a variable with the character class call 'Class'.
To access the buddy list from you you Buddy Package you'd could do
#SHOW @//Data/Variables/BuddyList.
or
#SHOW @//Data/Class
to show the class name
To change the value of the class name you could type
#VAR //Data/Class "Thief" ---- or ----- //Data/Class = "Thief"
If you wanted to add to you buddy list you could type
#ADDITEM //Data/Variables/BuddyList Zugg
[Edit] I acquiesce to the great Zugg on this. But one possible idea is to have an onConnect event in the package create the trigger at runtime so it creates and compiles the trigger using the session value of @BuddyList. For properly clean up I'd remove the trigger with the onDisconnect event as well. I may even prefer an approach like that because if you try to figure if a variable reference needs to be resolved at compile time or runtime can get tricky especially when there is a fairly simple solution. |
|
_________________ Asati di tempari! |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Thu Aug 16, 2007 5:38 pm |
If he just wants to create a shared package with the BuddyTrigger in it, then it will work fine for a single session. The problem comes if you want this package to work for multiple session windows at the same time. The pattern of a trigger gets compiled by the Perl RegEx library and cached until CMUD detects a change in the @BuddyList variable. So if you have multiple windows, each with their own @BuddyList, the trigger will only be working for one of the windows.
As long as you don't run multiple sessions at the same time, then there shouldn't be any trouble. A package with the Buddy trigger will work fine in that case.
Think of it this way: A given trigger can only have a single pattern that is active. So if you have multiple windows, and each window has it's own @BuddyList, then you really need a separate trigger for each window, since each window needs it's own pattern.
Remember that the Regular Expression pattern matcher doesn't know anything about CMUD variables. When you have a trigger with a pattern like {@BuddyList}, the variable gets expanded when the RegEx is created, to something like (Buddy1|Buddy2|Buddy3...). So the RegEx doesn't perform any variable lookups. When CMUD sees the @BuddyList variable change, then it recomputes the new RegEx pattern. So if each window has a different @Buddylist, then each window needs it's own different regular expression. |
|
|
|
Llwethen Novice
Joined: 08 Dec 2006 Posts: 37 Location: Lancaster,Oh
|
Posted: Thu Aug 16, 2007 10:33 pm |
Quote: |
If he just wants to create a shared package with the BuddyTrigger in it, then it will work fine for a single session. The problem comes if you want this package to work for multiple session windows at the same time. The pattern of a trigger gets compiled by the Perl RegEx library and cached until CMUD detects a change in the @BuddyList variable. So if you have multiple windows, each with their own @BuddyList, the trigger will only be working for one of the windows.
|
Well this explains why I get errors from triggers firing when a package gets used on a different character and I get message that say something like "Trigger fired but compiled is in error".
Quote: |
As long as you don't run multiple sessions at the same time, then there shouldn't be any trouble. A package with the Buddy trigger will work fine in that case.
|
I ran into this hard a few weeks ago when my mudd allowed multi-play for one weekend. Several of my sessions where so corrupt I had to rewrite everything.
Any chance that Cmud could just do a recompile when load a session instead so I could find immediately instead of when in battle? |
|
|
|
Arminas Wizard
Joined: 11 Jul 2002 Posts: 1265 Location: USA
|
Posted: Thu Aug 16, 2007 11:42 pm |
Tech's Solution of deleting the trigger ondisconnect and creating the trigger onconnect DOES recompile because it compiles when it creates. This would work great PROVIDED that you are not multiplaying.
As for things getting all corrupted you probably could have just ran the compatibility report and all would have been fine. Yes I know you probably did not have any errors. That is not the point. The compatibility report recompiles things. So if you wanted to do that manually before a session that should work as well. |
|
_________________ Arminas, The Invisible horseman
Windows 7 Pro 32 bit
AMD 64 X2 2.51 Dual Core, 2 GB of Ram |
|
|
|
Zhiroc Adept
Joined: 04 Feb 2005 Posts: 246
|
Posted: Fri Aug 17, 2007 12:36 am |
I didn't realize that the trigger would have access to the Window's settings. After some playing around, I was getting confused so I created the following test.
In the shared package: (though as far as I know, you can't do this by interactive commands)
Code: |
#CLASS Test
#ALIAS {testit} {#SH {}
#SH {From the alias}
#SH {inPackage = @inPackage}
#SH {/Test/InPackage = @/Test/InPackage}
#SH {Test/InPackage = @Test/InPackage}
#SH {inSession = @inSession}
#SH {/Test/inSession = @/Test/inSession}
#SH {Test/inSession = @Test/inSession}
#SH {inBoth = @inBoth}
#SH {/Test/inBoth = @/Test/inBoth}
#SH {Test/inBoth = @Test/inBoth}}
#ONINPUT {^yyy} {#NOINPUT
#SH {}
#SH {From the Trigger}
#SH {inPackage = @inPackage}
#SH {/Test/InPackage = @/Test/InPackage}
#SH {Test/InPackage = @Test/InPackage}
#SH {inSession = @inSession}
#SH {/Test/inSession = @/Test/inSession}
#SH {Test/inSession = @Test/inSession}
#SH {inBoth = @inBoth}
#SH {/Test/inBoth = @/Test/inBoth}
#SH {Test/inBoth = @Test/inBoth}}
#VAR /Test/inPackage {inPackage}
#VAR /Test/inBoth {inPackage}
#CLASS 0 |
And in the Window:
Code: |
#VAR /Test/inSession {inSession }
#VAR /Test/inBoth {inSession} |
Then do "testit" followed by "yyy". You get:
Code: |
From the Trigger
inPackage = inPackage
/Test/InPackage =
Test/InPackage =
inSession = inSession
/Test/inSession = inSession
Test/inSession = inSession
inBoth = inSession
/Test/inBoth = inSession
Test/inBoth = inSession
From the alias
inPackage = inPackage
/Test/InPackage = inPackage
Test/InPackage = inPackage
inSession =
/Test/inSession =
Test/inSession =
inBoth = inPackage
/Test/inBoth = inPackage
Test/inBoth = inPackage |
Note that:- the trigger can only see the Package variable using "@inPackage" with no class specifier.
- the trigger sees a Session variable overriding the Package variable
- the alias cannot see the Session variable at all
- the alias sees the Package variable overriding the Session variable
Are these differences intended? |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Fri Aug 17, 2007 1:09 am |
As I said, a *trigger* executes in the context of the window that received the text. An *alias* executes within it's normal package and class. This explains why the trigger could see your session variable, but the alias could not.
This is something that I'm considering changing as I've been working on v2.0, but I'm not sure what the side effects might be yet. In 2.0 I am considering having the alias execute in whatever the "current" context is, unless the alias is placed within a Class that has the "Set as Default class" option set. If I make that change, then your alias would also be able to see the session variable, since the alias would be executed within the "current" context, which is the context of the window that you entered the command line in.
In all cases, CMUD always looks first in the current class (then it's parent, then it's parent, etc) first. Only if a variable is not found in the current class scope does it start looking in other packages and other windows. That explains why the trigger could also see the @inpackage variable.
When you start using the /Class/Name syntax, it's again using the "context" of the trigger or alias. So for the trigger, it's looking in the Window, and for the Alias, it's looking within the other package. You can force it to look in a specific window or module using the //ModuleOrWindowName/Class/Name syntax. That's actually one way that your package can actually reference variables within the Window, even though the Window is normally private.
Yes, this stuff can get very complicated. Scoping rules usually are. I'm trying to make it work as you'd normally expect it to, which is why Triggers work within the Window context and not the package context. Like I said, I'm considering making this the case for other settings as well, but it's really easy to do something wrong that causes a lot of existing scripts to break, so I have to be careful with this stuff.
But the concept of Packages is still pretty new, so I'm open to discussion on how this should really work. |
|
|
|
Zhiroc Adept
Joined: 04 Feb 2005 Posts: 246
|
Posted: Fri Aug 17, 2007 3:38 am |
I'd vote to have scoping work the same way from either a trigger or an alias. I think it would be far less confusing to a user to remember the rules one way. Also, since aliases and triggers sometimes call each other, it would probably induce bugs for it to work differently.
One thing strange about scoping is that there seems to be no way to refer to packages directly. There's //ModuleName, but Module names are not guaranteed to be unique. It would seem to be good for a package to be able to guarantee that it's reading and executing it's code, rather than something that might come from some other package. With the package library, it would seem to be important to be able for authors to write code that isn't affected by whatever else a user might have installed.
An idea would be to use: @Packagename://ModuleName/Class/.... And then, to allow: @://ModuleName/Class/... or even @:/Class/.... A blank PackageName refers to the package the currently running setting (alias, trigger, or event) was defined in. This allows a package to not get hijacked by non-package data.
In fact, I might think again about searching packages for settings by default. Code should either look in the "current" scope, or the code should quantify the scope. All sorts of bugs can be causes by two packages inadvertently using the same name for a Module or setting. Or user code using the name of a variable that is defined somewhere in the package, and then setting it (like that @inPackage example). If sharing packages are going to be useful, it's got to be made somewhat hardened against errors like this. |
|
|
|
Dharkael Enchanter
Joined: 05 Mar 2003 Posts: 593 Location: Canada
|
Posted: Fri Aug 17, 2007 5:29 am |
As a work around, how about creating the trigger in onconnect and ondisconnect and use the %sessionnum as the id.
That way a unique trigger would be created for each session.
As an improvement on this Zugg could add a "session" option for triggers, allowing CMUD to create and destroy unique triggers automatically when a session begins and ends.
SessionStart and SessionEnd events might be nice too allowing you to setup variables etc unique to each session. |
|
_________________ -Dharkael-
"No matter how subtle the wizard, a knife between the shoulder blades will seriously cramp his style." |
|
|
|
Zhiroc Adept
Joined: 04 Feb 2005 Posts: 246
|
Posted: Fri Aug 17, 2007 5:45 am |
Unfortunately, it looks like Events are run in the context of the package, so triggers created in OnConnect are created in the Package, and not the Window, so that doesn't help directly. You'd have to have an OnConnect in the Window itself, which breaks the reason for using a package.
|
|
|
|
Zhiroc Adept
Joined: 04 Feb 2005 Posts: 246
|
Posted: Fri Aug 17, 2007 2:51 pm |
As I think about it more, I think that variables and probably aliases need to have a "public/private" option for packages, meaning whether they get searched for by default if not found in the current context.
Variables are more likely to be private than public. But aliases might be more public than private. Triggers and events are only public. |
|
|
|
Fang Xianfu GURU
Joined: 26 Jan 2004 Posts: 5155 Location: United Kingdom
|
Posted: Fri Aug 17, 2007 6:01 pm |
If by public/private, you mean whether or not scripts outside the current package can search for them, this is already possible. Create a local module in your package and put the aliases in there.
I originally thought that the difference in alias/trigger behaviour was a bit bizarre, too, but then I realised that triggers are always called by text appearing in a window. It makes sense for them to know which window fired them. Aliases on the other hand can be called by scripts anywhere. |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Fri Aug 17, 2007 6:47 pm |
Yeah, this stuff isn't nearly as easy as you might think it should be.
First, adding another syntax to refer to a specific package file isn't going to be easy, and isn't something that I can add soon. That kind of massive syntax change can have all sorts of bad side effects.
In general, packages should not interact with each other. That's the whole idea of a package. They are supposed to be self-contained. People who create a package with Global or External modules need to be sure that those module names are unique. There are plenty of ways a designer could name their module to make it unique.
As I already stated, even when executing a trigger it will still look for a variable within the package itself first, before checking the window. So package designers just need to be sure that any variables that they want to use privately within the package are already defined in the package. As your example showed, the trigger can use @inPackage to reference a variable within the package.
If you find yourself using the @//Module/Class/Name full syntax a lot, then that's usually an indication that your package is not self-contained and you should re-think what you are trying to do.
As Fang mentioned, there are already ways to mark a module as public or private. So you don't need to add a public/private setting to every variable. You just put the variable into the proper public or private module.
The reason an Alias always executes within it's own package context is that an Alias is very much like a procedure or method in a C++ class. When the alias runs, you expect it to have access to the settings within the package by default, just like running a subroutine within a module, or a method within a class.
Events are very much like Aliases, so again, they run within the context of the package. Typically you would use events to initialize various aspects of your package (set default variables, etc). It is ok that it creates the Triggers within the Package and not the window. When your package triggers run, they already run in the proper window context.
The problem you are dealing with is that you want to create triggers within the specific window that is being connected so that you can create a unique trigger for each window. This is a very specific problem, so let's try to solve that problem rather than screwing around with the entire scope system. The existing scope system exists to maintain compatibility with what you could do in zMUD, and to keep packages as independent as possible.
So, let's try to focus on specific things you need to do (like create a trigger for another window/module) and see if those things can already be done with the current system or not.
Currently, the *rule* is that everything executes within it's own package context. The only exception to the rule is Triggers, because a trigger is always executed by text appearing in a window. So it's currently Triggers that are the exception. Making that the rule instead of the exception would have far-reaching consequences and is something that we would need to give a lot of thought to. There has already been over a year of development and testing of the current system, and we need to make sure we understand all of the ramifications before making some kind of massive change like that.
If you have other programming experience, try to compare CMUD programming with what you are already used to. Every language has scoping rules. A "class" in CMUD is very similar to a "class" in C++ (which is why they were named that way in the first place). So if a variable doesn't exist locally, CMUD looks in the parent class, then in it's parent class, etc, just like a class hierarchy in C++.
A "module" is a collection of classes, so it's like a "unit" in Delphi/Pascal (not sure what the C++ term for it is). You can have private modules that are only seen by other modules in the same package, or you can "publish" a module and make it visible to the outside world. A "window" is just a private module that has a user-interface associated with it. Windows are private to prevent unexpected interaction between multiple windows.
A "package" is a collection of modules and windows. It's similar to a "project" in Delphi or C++. You might also think of a package as a "library", where the library consists of an external module that contains a bunch of useful methods (aliases) or functions. You make these aliases and functions general purpose by passing arguments to them rather than relying upon global variables, just like you would in any other programming language. You want to keep your code "modular" and reduce dependencies between packages. Having one CMUD package depend upon another is similar to having a C++ library that depends upon another. Certainly this happens sometimes, but it's better if you can minimize it.
So, think about executing a method (alias) in some general purpose library (package). If the method referenced a variable, where would you look for that variable? Every language that I'm aware of would look for that variable within the library itself. It wouldn't make much sense for it to look for the variable in the calling program. The library routine doesn't have any knowledge of the calling program. |
|
|
|
Zhiroc Adept
Joined: 04 Feb 2005 Posts: 246
|
Posted: Sat Aug 18, 2007 12:56 am |
I intended a much more long-winded response, but that veered way off into tangents :)
Quote: |
As I already stated, even when executing a trigger it will still look for a variable within the package itself first, before checking the window.So package designers just need to be sure that any variables that they want to use privately within the package are already defined in the package. |
That's not what my test showed (see above). The trigger found @inBoth = inSession, so it saw the session's variable first.
After doing some more playing around, I found I could get the trigger to reference a package variable using the full name (//Mod/Class/var). And, I could get an alias to see a Session's variable using //Window/Class/var. But this latter isn't useful, since you can't have the package alias tied to a Window. (If you did, why bother with the package, just import into the Session).
I made a local-only module (Mod_private), and created a variable and an alias in it. It turns out that a package trigger can't reference this variable except by using //Mod_private/var, and I can't get it to run the alias at all.
BTW, even a Session alias can read this "private" variable, and can also modify it. I guess "local-only" just refers to whether it is found without using the full //Mod/var syntax?
Also, what's the difference between External and Global?
So let's get back to the Buddy package for a concrete problem As I have it defined in zMUD, it has the following features:
a trigger to colorize names listed in @buddylist
an alias "budadd Name" to add a name to @buddylist
an alias "budrm Name" to remove it
an alias "budshow" to show the list
@buddylist has to live in the Session's settings.
In it's simplest form, "budshow" would have to be:
Code: |
#ALIAS budshow {#SH @/Buddy/buddylist} Buddy |
But unfortunately, since buddylist is in the Session, and budshow is an alias, I can't get it to work. I suppose I could change budshow to a command trigger? That seems rather inobvious, but I guess it would work.
And, I guess I have to add just one thing. in my view, z/CMUD classes have nothing to do with "classical" :) Classes as one sees in C++ or Java. Since zScript doesn't have anything close to objects, the concept of methods and classes really doesn't apply. Classes in z/CMUD are more like pathnames in a directory structure, and have little more relevance than as a namespace divider, with a little sugar added in to find settings without having to specify full names (which can sometimes get you into trouble, when names aren't unique). |
|
|
|
Fang Xianfu GURU
Joined: 26 Jan 2004 Posts: 5155 Location: United Kingdom
|
Posted: Sat Aug 18, 2007 1:32 am |
Zhiroc wrote: |
I guess "local-only" just refers to whether it is found without using the full //Mod/var syntax? |
You guess right.
Zhiroc wrote: |
Also, what's the difference between External and Global? |
External settings are only visible outside the package that contains the module. So an external trigger wouldn't fire on text in a window in the same package. It's intended for use with channel capture packages and the like - you'd have triggers in the package that you wouldn't want activated by text in the capture window.
EDIT: More info on this strange behaviour here. |
|
|
|
Dharkael Enchanter
Joined: 05 Mar 2003 Posts: 593 Location: Canada
|
Posted: Sat Aug 18, 2007 11:44 pm |
When triggers are fired manually or by alarm what context are they running in Package or Window?
And OnConnect and OnDisconnect Events are triggered by a specific Session so dont they have some ideas about which session triggered them? |
|
_________________ -Dharkael-
"No matter how subtle the wizard, a knife between the shoulder blades will seriously cramp his style." |
|
|
|
|
|
|
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
|
|