I've admitted before that one reason I maintain this blog is as an attempt to combat my natural inclination to procrastinate over new releases. There is certainly some work involved in releasing a new version of a product, albeit much less than introducing a completely new product, and I'm sometimes reluctant to incur that overhead without a pressing reason such as a significant bug.
The problem with the upcoming releases of Mesmerizer 1.5 and The Enforcer 1.0 isn't so much procrastination as it is that I've kept coming up with ideas for exciting new features. This is especially true of The Enforcer. I had expected the 1.0 release to be primarily a stability release, with just a few minor functional enhancements that I mentioned earlier. However, partly as a result of the refactoring work I've done on The Enforcer, I've been using it to perform much more complex programming tasks, and that has given rise to many ideas for enhancements.
And that is the secondary purpose of this blog - by pre-announcing an upcoming release, I sort-of commit myself to a time-frame for that release, which limits the amount of feature-creep that can slip into a given release. I expect what I'm documenting below, along with the previous post, to describe the complete set of new features in the two product updates. I have more features in-plan, but they can wait for the subsequent release.
Mesmerizer 1.5
Random Outfits
One other new feature slated for the upcoming Mesmerizer 1.5 release is random outfits. While there's nothing stopping you using it with a flat folder structure, this feature is meant for use in conjunction with hierarchical outfit folders. The command, randoutfit, takes a single parameter - the path of a folder within the Outfits hierarchy. This path must be a folder, not an outfit, so if you have only a flat folder structure, the only valid parameter is the Outfit root folder, or "/". The command will pick an outfit at random from among the outfits below the give path, and will change into it.
randoutfit is primarily intended for automatic outfit selection upon login (or some other event), and allows the wearer to collect a group of candidate outfits beneath a folder, from which the command will pick one to wear. It is particularly well-suited for use remotely via The Enforcer, since it saves you having to program multiple wearoutfit commands, and having to update those commands whenever the wearer changes their stored outfits.
As an example, assume that the wearer has created a folder for P9-style outfits under #RLV called #RLV/Outfits. Inside that folder, they might create a subfolder called LoginOutfits, and inside LoginOutfits, they could put multiple outfits.
Assuming the LoginOutfits folder contains at least one outfit, a login trigger in their Mesmerizer, or a login event handler in The Enforcer could issue a randoutfit "/LoginOutfits" command, which would cause them to wear one of those outfits at random on each login.
If there is a hierarchy of folders beneath the given path, randoutfit will traverse the hierarchy. It does this by making an independent choice at each folder level, and only if that choice selects a subfolder (rather than an outfit) will that subfolder be traversed. Also, empty folders are ignored in the traversal.
The randoutfit command will inform its issuer which outfit was selected, which is especially useful if the wearer is remote. That notification has been added to the existing wearoutfit command as well.
Random Outfits and the new menu command (described in a previous post) are part of a new thrust towards supporting more flexible Mesmerizer programming. Such programming can be done either in Mesmerizer triggers or Enforcer notecards, but the main focus is on The Enforcer, since it's not subject to the memory limitations of the Mesmerizer, and can therefore support much more complex programming. Also, conditionals are key to creating interesting programming, and while the Mesmerizer does have the executeif command, true conditionals are better served in The Enforcer with its much more flexible set of conditional commands.
Enforcer 1.0
Namespaces
As part of the refactoring to gain more memory overhead for The Enforcer, namespaces have risen to become much more concrete objects than they were previously. I already mentioned them in the previous post without fully explaining what they are, so I thought I should do that here.
A namespace is simply a collection of variables and their values. When a program notecard is executing within The Enforcer, each line is executed more-or-less in isolation, with the only real "context" being the variables that successive lines share. Whenever an event causes a program notecard to begin execution, The Enforcer creates a new namespace for that execution, initialized with a few standard variables (like $name), and then that namespace is used by the individual lines of program. This means that the variable definitions that a program makes are only visible within that particular execution of the program. This is almost always what's wanted, although there are a few specialized use-cases where you might want variable values to persist across executions.
A new namespace is also created when a program invokes a subprogram via the "@" action-character. For instance, if Notecard1 is running, and it executes a line that contains @Notecard2, then execution of Notecard1 will be paused, and Notecard2 will begin executing, using a copy of Notecard1's namespace. Since Notecard2 is using its own copy of the namespace, any changes it makes to variables will not be visible in Notecard1 when Notecard2 exits. Again, this is usually what's wanted, since it prevents Notecard2 from accidentally overwriting variables that might be important to Notecard1. However, it does make it impossible for Notecard2 to pass information back to Notecard1.
To support returning values from called subprograms, I have decided to go initially with the "shared namespace" approach discussed in the previous post. Enforcer 1.0 will introduce an alternative action-character, "&", to mean "call with the current namespace". So the commands @Notecard2 and &Notecard2 each invoke Notecard2 as a subprogram, pausing the current program until execution of Notecard2 completes, but whereas @Notecard2 will give Notecard2 a copy of the current namespace, &Notecard2 will allow Notecard2 to share the current namespace, so that any variable changes that Notecard2 makes will be visible in Notecard1 after Notecard2 has returned.
This also offers a way to share variable definitions between scripts. For example, supposed that you have an event script that, among other things, displays a "message of the day" when a sub logs in, via the Mesmerizer's text command. When you want to change that message, you could edit the parameter of the ^text command in the login script, but it's cleaner to put the message into a variable. That way, if you have multiple scripts that want to use the same message, you could specify the message in a single script called, say DayMessage, as follows:
!setvar motd::This is today's message
Then, any event script that wishes to use the message of the day would simply invoke the DayMessage script using the & call:
&DayMessage
This would define the variable motd in the event script's namespace, where it could be used in a ^text or !sendim command.
This is a good way to collect definitions that are needed by multiple event scripts into a single notecard, and works well for static configuration data - the sort of data for which it's ok to edit a notecard when you want to change it. However, it doesn't help if you need your scripts to be able to change data, and have those changes preserved across multiple events (or multiple invocation of the same event). For instance, let's say you wanted to count the number of times that a particular sub has logged in. To do that, you'd need to be able to set a variable in one event, and then read it in another.
Persistent Namespaces
Namespaces are usually created and destroyed automatically by The Enforcer as part of the data belonging to an event. However, there is sometimes a need to preserve data across events, and persistent namespaces address that need. A persistent namespace is just like any other namespace - a collection of variable names and their values - except that it doesn't "belong" to any specific event invocation. Instead, it is explicitly created either via The Enforcer's menu, or by an event script.
Before I go into the details of creating a persistent namespace, I'll discuss how it's used. Persistent namespaces have names, and for the purposes of discussion, let's assume that there exists a persistent namespace called "PreservedValues" that contains a variable called "counter" with the value "3". To access that value, an event script would use the new !attach command as follows:
!attach PreservedValues::counter
What this does is create a regular variable called "counter" in the event's namespace, and set it to the value of the counter variable from the PreservedValues namespace. If counter didn't exist in PreservedValues, it will be created, and initialized to an empty string. This typically means that, if the counter variable is to be treated as an integer, you would follow the above !attach command with something like:
!if 'X$counter' 'X' streq
!setvar counter::0
!fi
The local counter variable can then be modified, and then put back into the PreservedValues namespace with the !detach command:
!detach PreservedValues::counter
When an event script executes an !attach command, the variable in the persistent namespace is "locked", in such a way that any other event attempting to invoke !attach on that same variable will be made to wait until the first event either completes its !detach (on the same variable), or terminates. If a script terminates with some variables still attached, the corresponding variables in the persistent namespace are simply unlocked - they are not automatically updated.
Under the covers, !attach creates a link between the variable in the persistent namespace and the event script's own namespace, and the link is removed either when a script running with that namespace executes a !detach (which also copies the current value of the variable in the local namespace into the corresponding variable in the persistent namespace), or upon deletion of the local namespace (which occurs automatically soon after the last script using that namespace terminates). The fact that the link is to the namespace (rather than to the script) means that if a lock is established in one notecard, it will be held by subprograms invoked with the & action-character, but not in subprograms invoked with the @ action-character.
Finally, if a script just wants to read the current value of a variable, without locking it, it can use the !get command:
!get PreservedValues::counter
This simply copies the variable from the PreservedValues namespace into the event's namespace, without performing any locking (or waiting for locks to be released).
A persistent namespace can be created via the "Namespaces" button on The Enforcer's main menu. This will prompt for the name of the namespace, and once it's created you can also define variables within it from the menu. Persistent namespaces can also be created from an event script via the new !namespace command:
!namespace PreservedValues
This will create an empty namespace called PreservedValues. If the namespace already exists, the command is a no-op. To simplify namespace creation, I recommend creating a Startup notecard which contains !namespace commands for all namespaces your other event scripts use. Startup can also use !attach/!detach pairs to initialize variables within the new namespaces. Startup is not currently a defined event, although it may become one in the future, but you can manually invoke it by generating a Startup event via The Enforcer's menu.
Currently, the only way to delete persistent namespaces is via the menu.
I have several future additional uses in mind for persistent namespaces, but as I said above, I want to get the V1.0 release out before I turn to additional feature work.
Events
Here is a list of the current "standard" events that The Enforcer understands.
| Event |
Description |
| Logon |
A user establishes a session with a Hub |
| Login |
A user wears their Mesmerizer, or logs in to SL while wearing their Mesmerizer |
| Logoff |
A user's session with their hub times out |
| Address |
A connected hub allocates a new HTTP address |
| Location |
A user moves within SL |
| Update |
Periodic "keepalive" event |
| Menu |
User makes selection from a menu |
| Custom Event |
User's Mesmerizer invokes the signal command |
These events use the following notecards, and define the following variables:
| Event |
Notecard(s) |
Variables |
| Logon |
Logon:<name> |
name |
| Login |
Login:<name> |
name |
| Logoff |
Logoff:<name> |
name |
| Address |
Address:<hubname> |
hubName, hubAddress |
| Location |
Location:<name>:<sim> |
name, location, sim, x, y, x |
| Update |
Update:<name>:<sim> |
name, location, sim, x, y, x |
| Menu |
Menu:<name> |
name, data |
| Custom Event |
Event-name:<name> |
name, data |
The notecard names in the above table have a fixed part that corresponds to the name of the event (e.g. Location), and one or two qualifiers, separated from one another and from the event name by colons. Qualifiers are indicated in the table by being enclosed in angle-brackets, but that's just to make them stand out in the table - the angle brackets should not be used in the actual notecard names. These qualifiers limit the applicability of the notecard so that it is only invoked for the event when it occurs for the specified person or other qualifier. For instance, a notecard named "Login:Nue Broome" would be invoked each time I logged in, but not if someone else were to log in. To create a notecard that would fire for anyone logging in, use "*" in place of the name, i.e. a notecard called "Login:*". Only the most specific notecard will be invoked for an event, so if The Enforcer contained both of the above notecards, Login:Nue Broome would be used whenever I log in, and Login:* would be used for everyone else. The name of the user is considered more "specific" than the sim they're in, so if a Location event were fired for me in the "Sleepy Hill" sim, and The Enforcer contained the following Location notecards: Location:*.*, Location:Nue Broome:* and Location:*.Sleepy Hill, then the notecard that would be invoked would be Location:Nue Broome:*.
Currently, the matching of notecard names is done in a case-sensitive manner, so a notecard called Login:nue Broome would not work. I may lift that restriction in the future, so you should avoid using notecards whose names differ only in the case of some of their letters. There is a new Validate option in The Enforcer's menu, which goes through all the notecards and verifies that the user-names provided are canonical Legacy Names with correct case (e.g."Nue Broome" or "Fenella27 Resident"), and suggests corrections for any that are not.
No comments:
Post a Comment