Wednesday, July 15, 2020

Another Preview - Enforcer V1.0 and Mesmerizer 1.5

Enforcer V1.0


As promised at the beginning of the year, I intend to release the product version of The Enforcer soon.  The main improvement over the preview version is better memory use, which results in improved stability, as well as some bugfixes.  I've also renamed the scripts so that they all begin with a tilde "~".  This makes the scripts appear at the bottom of the content tab, rather than being mixed in with user-created event notecards.

There are also some enhancements to the scripting language, described below.  

List Indexing

List indexing has been in the Mesmerizer for quite a while, but hasn't previously been available in the Enforcer.   As a quick recap to how it works, consider the string ":who:what:i dont know:".  This is an example of a list - a string starting and ending with colons, and containing elements separated by colons[1].  So the above is a list containing three elements, with the values "who", "what" and "I dont know".  Lists are used by the Mesmerizer in several places, notably the precision attachment stripping and outfits features, each of which generates a list which can then be indexed to refer to a specific attachment or outfit.

Assume that a variable called on contains the above string.  Then $on would be the entire list value ":who:what:I dont know:", and the individual elements can be obtained by using a dot followed by the number of the desired element.  So $on.1 would be "who", $on.2 would be "what" and $on.3 would be "I dont know".

The main reason I've added list indexing to The Enforcer is to better support the use of custom events, generated by the Mesmerizer's signal command.  A custom event allows for a condition within the Mesmerizer to send a signal to The Enforcer, which can then perform some action in response.  The signal command takes two parameters:  The name of the event to be signaled, and "parameter data", which is available in The Enforcer's event as a variable called "data".  So if a connected Mesmerizer signaled an event as follows:

signal "myEvent" "myData"

then this could be caught in an event notecard in The Enforcer called "myEvent:*".  Enforcer script within that notecard can use the standard variables such as $name to give the full name of the Mesmerizer wearer, or $location to give their position within Second Life.  Custom events may also obtain the parameter data that was passed to the signal command as $data.  In this case, $data would contain the string "myData".

The above is fine if you only need to pass a single parameter to the event handler, but it's clumsy if you need to pass multiple parameters.  The obvious solution is to pass a list as the parameter data, for example:

signal "myEvent" ":FirstParameter:2ndParameter:"

Now when the Enforcer event fires, $data would contain the string ":FirstParameter:2ndParameter:", and since this string is a list value, the event script could read the first parameter as $data.1, giving "FirstParameter" or the second as $data.2, giving "2ndParameter".

In addition to the dot-notation above for obtaining a specific element from a list variable, The Enforcer has also acquired an element operator that can be used within expressions.  element takes two parameters, the first being the list, and the second being an integer specifying the position within a list.  As an example, using the data variable above, the expression:

$data 1 element

would select the first element from the list in data, giving "FirstParameter", just like $data.1.  While the dot notation is part of variable expansion and therefore can only be used with a variable and a fixed integer, element is a proper operator, and can therefore be used with arbitrary values, for example the expression:

':who:what:why:' $x element

would evaluate to "who" if x contains 1, "what" if x contains 2 and "why" if x contains 3 (and an empty string if x contains something else). 

$daytime pseudo-variable

The Enforcer supports a number of "pseudo-variables" - objects that are accessed like variables, but which are actually calculated on-demand.  An example is $datetime which always returns the current date and time.

To better support the programming of curfews, I have added $daytime, which returns the number of seconds since midnight.  $daytime joins the existing set of time-related pseudo-variables:

  • $datetime - returns a date/time, e.g. "Mon 2018-07-02 21:45 PST"
  • $time - the time-part of the above, e.g. "21:45"
  • $day - the day-part of the above, e.g. "Mon"
  • $date - the date-part of the above, e.g. "2018-07-02"
  • $daytime - the number of seconds since midnight, e.g. "78325"
Note that while the other time-related pseudo-variables have a resolution of one minute, $daytime is accurate to one second.  All time values are expressed in the SL Time-zone (PDT/PST).


Online checking

I've sometimes wished that I could program different Enforcer responses depending on whether or not I'm online.  The new checkonline command allows for this.  Its syntax is very similar to the setvarex command:

!checkonline myVar::Nue Broome

This will set variable myVar to true or false depending on whether or not I'm currently online.  Subsequently, $myVar can be used in a conditional or other expression.

The first parameter, in this case myVar, gives the name of the variable to set;  the second parameter, in this case "Nue Broome", specifies the avatar to be checked.  You can specify names using either "Firstname Lastname" or "firstname.lastname" formats, and if lastname is omitted, "Resident" is assumed.  You can also specify a user key instead of a name.

Note that there is a bug in LSL dating back almost ten years that means that checkonline may occasionally indicate that a given user is online up to ten minutes after they have actually logged out.


Local Events

In addition to the scripting enhancements above, Enforcer events can now be generated locally, via the menu.  A new Event menu choice allows you to specify the name of an event, the intended target, and any required variable definitions.   This lets you invoke a programmed response at any time, rather than having to depend on the target's actions triggering the event.   It can also be used to invoke script notecards that aren't directly associated with a "normal" event.

Note that local events will only cause event scripts to run - they are not treated as "real" events by The Enforcer.  For example, if you generate a Login event for a user, it will cause the appropriate Login notecard to be invoked, but it will not cause The Enforcer to believe that the user is actually logged in.

You can use local events for a number of things, including creating notecards to perform actions that can be run on-demand, and testing normal event notecards by generating their trigger events.


Scoping and returning values

Unlike the above features which are already implemented and working, the scoping/return feature is still somewhat on the drawing-board, so it may not make the V1.0 release.  If it doesn't, it will follow pretty soon.

The issue is to do with calling one Enforcer notecard from another.  The @ action-character allows for this today: Notecard_1 can "call" Notecard_2 simply by containing the line:

@Notecard_2

This will pause execution of Notecard_1, and cause Notecard_2 to run.  When execution reaches the end of Notecard_2, or an !exit or !exitif command is encountered, Notecard_2 will stop, and Notecard_1 will resume from the line after the call.

This allows you to easily divide your Enforcer scripts into subroutines, contained in separate notecards.  For instance, you might have a notecard that sets up some RLV restrictions that you want to be applied to some subs on login.  You could put a series of ^mayXXX commands in the Login notecard of every sub that you want the restrictions to be applied to, but it's much better to create a single notecard, perhaps called "Restrictions", to hold those commands, and then have each sub's Login notecard call that Restrictions notecard.  That way, if you want to change the set of restrictions, you only have to edit one notecard.

When Notecard_1 calls Notecard_2 with an @Notecard_2 command, Notecard_2 is given a copy of all the variables that were defined in Notecard_1 at the point of call.  So Notecard_2 can use $name to get the full name of the user who caused the event to run, as well as read any variables that Notecard_1 explicitly set with !setvar or !setvarex.  But because Notecard_2 gets a copy of those variables, any changes that Notecard_2 makes won't be visible in Notecard_1 after Notecard_2 exits.

Normally, this is good, because it means that Notecard_2 can make use of any variables it likes without having to worry about accidentally overwriting a variable that Notecard_1 might be using.  However, it means that there's no way for Notecard_2 to pass data back to Notecard_1.  Providing a way to do that is what this feature is about.

I'm considering three different approaches, and may end up implementing any one, two or all three of them.

The first is simply to create a way for a notecard to indicate that its variables should be shared with a second notecard that it calls, as opposed to being copied.  That way, any changes to variables that Notecard_2 makes will be visible in Notecard_1 when Notecard_2 exits.  This is the simplest method, and would likely be realized by defining a new "action character", possibly "&", to indicate a call in which variables are shared rather than copied.  So while @Notecard_2 would call Notecard_2 with a copy of the current variables, &Notecard_2 would call Notecard_2 with the actual current variables.

The second approach is to provide a way for a called notecard to explicitly return a value to the calling notecard.  This would require a new command to indicate a call that also specifies a variable to receive a returned value, as well as variants of the !exit and !exitif commands that would return a value.  If more than one value needs to be returned, the list mechanism described above could be used, returning multiple values as elements within a single list value.

The third, and possibly most interesting approach, would allow for namespaces to be explicitly specified when reading or setting variables.  This is interesting because it has the potential to allow communication between separate events, which would allow complex conditions to be evaluated.

At the moment I'm leaning towards implementing both the first and second methods - the first because it's quite trivial to implement, and the second because it's "clean".  I will likely implement the first method, and see what its impact on memory usage is, and then decide whether to implement the second as well.

Summary

The Enforcer is easily the most complex product in the CHAOS range, not so much in terms of lines of code - that honor goes to the Mesmerizer - but in terms of the amount of concentrated effort that has gone into its creation.  It's also, as far as I know, unique within SL.   When I move it to full product status, this reality will be reflected by a small increase in price.  Those of you who purchased The Enforcer during the extended Beta period will, of course, be entitled to the product version (and future updates) at no additional cost.


Mesmerizer 1.5


Menu command

One reason to want to better support multiple parameters to events is a new Mesmerizer feature, currently slated for V1.5.  This is a way to allow the Mesmerizer to offer some degree of interaction with the wearer.  While the Mesmerizer (and The Enforcer) allow you to program responses to various external events (the wearer moving to a sim, for instance, or logging in at a particular time), it is sometimes desirable to give the wearer an option to override those programmed responses.  For instance, let's say that you have defined a trance sequence to be played to the sub upon login, and that this trance sequence takes 15 minutes to play.  If the sub wants to quickly drop in to SL to purchase something in a sale, or to pay rent, then being forced to sit through a 15 minute trance is likely to be intensely annoying.   It would be nice to display a menu to the sub on login, asking them whether they want to skip the trance on this login.  Alternatively, the trance script itself could display a menu at a convenient point within the trance, giving the sub the choice of skipping to the awakening portion of the trance.

There are many other situations where a programmed response might want to collect some information from the wearer to choose between different actions.

Mesmerizer 1.5 will introduce a menu command, which allows a trigger-rule (or Enforcer event) to display a menu from which the wearer has to choose a response.  There will likely be two variants of the menu command, a simpler one:

menu <menuName> <menuText> <menuChoices>

and a slightly more complex one:

menu2 <menuName> <menuText> <menuChoices> <timeout> <default>

<menuName> is a string that will be used to identify this specific menu.  <menuText> is the text that will be displayed at the top of the menu-box.  <menuChoices> is a list of button labels.  There must be no more than 12 elements in the list, and no element should be longer than 12 characters.  The menu2 command has 2 additional parameters:  <timeout>, which is a float that specifies how many seconds to wait for a response, and <default> which is a string that will be returned if the timeout expires.  The simpler menu command simply defaults <timeout> to being 300 (5 minutes) and <default> to be the string "timeout".

When the wearer makes a choice (or the timeout expires), the new on-menu event will fire, and the variable menuid will be set to whatever was specified for <menuName> in the menu or menu2 command, and the variable select will be set to the choice that the wearer made (or the <default> string, if the timeout expired).

As an example, the following command:

menu "myMenu" "Pick one of the following:" ":Yes:No:Maybe So:"

would present the wearer with a menu asking them to pick one of "Yes", "No" and "Maybe So".  When they choose one of those options, or the 5 minute timeout expires, the on-menu event will fire, with $menuid being set to "myMenu", and $select being the label they chose, or the string "timeout".

If the Mesmerizer is connected to a Hub monitored by The Enforcer, then an Enforcer event called Menu will also be generated.  In addition to the standard event variables such as name and location, this event will be passed a variable called data containing a list value with two elements: the menuName (in this case "myMenu") and the chosen response (or default value if the timeout expired).  So if the wearer had picked "No", the value in variable data in the Menu event would be ":myMenu:No:".  With the new list indexing support above, the menuName can be obtained as $data.1, while the selection made can be read as $data.2.


Additional group support

I added the setgroup command to allow the Mesmerizer to change the active group back in V0.99g.  V1.5 augments this with the maygroup and getgroup commands.

maygroup lets you control whether the wearer may change their active group.  Like most of the mayXXX family of commands, it takes a Boolean parameter to indicate whether or not the ability is allowed.  So maygroup no will prevent the wearer from changing from their current group, while maygroup yes will allow them to change their group.

getgroup tells you the name of the group they currently have as active.   This is now also included in the information returned by the report command.



[1] While list values should both start and end with colons, some list-handling functions are currently tolerant of improperly-formed list values that omit one or both of these colons.  A future enhancement to lists will likely make the leading and trailing colon mandatory everywhere, so you should not rely on this behavior, and instead ensure that all list values are properly introduced and terminated with a colon character.

No comments:

Post a Comment