Introduction

In response to the enhancement suggestion in Mantis #285, there is a second Mantis #2106 which is a proposal for a series of new name registration functions to be used in combination with the functions described in the first discussion.

This blog posting ties it all together.  I tried to update the InWorldz wiki but the format of a wiki is such that each function gets it’s own page, making the overall proposal less readable.  It’s also difficult to distribute a document to the residents of a virtual online community so in the interest of presenting the proposed interface definitions, I’m just going to blog it below.

Note: At this point, this is still just a proposal.  However, it has already undergone four iterations with Mantis users and among the InWorldz developer team, with significant changes each time, but it is now firming up.  That said, if anything leaps out as inadequate or a problem, feel free to raise it as a concern or suggestion.

Summary

The general idea is that we need to be able to send messages addressed to arbitrary prims anywhere on the grid in a fast, efficient and reliable manner.  We should be able to address the recipient prim by it’s key (UUID), but since a new key is assigned when an object is rezzed, we also need some way to look up the current key for an object by some other identifier that is unchanging.  Most people are familiar with the DNS registrations that translate domain names into IP addresses.  This proposal adds a similar standard built-in registration for name-to-key lookups.

Here is a quick summary of the proposed interface:

IW_SCOPE_REGION  0    // this region only
IW_SCOPE_GRID    1    // grid-wide
iwRegisterName(string name, integer scope)
iwDeregisterName(string name, integer scope) // rarely needed
list iwGetRegistration(key ownerKey, string name, integer scope)
  // Returns: [ regionID, primID, scriptID ]
key iwGetRegistrationKey(key ownerKey, string name, integer scope)
  // Returns: primID
integer iwMessageObject(key object, string message)
integer iwMessageRemoteObject(key region, key object, key script, 
            string message)

iwRegionShout(integer channel, string message)

Detailed Description

Below is a more detailed description of each function, along some examples to show general usage.

iwRegisterName(string name, integer scope)

This function registers the current script context under the specified name (alias). This allows an unchanging name to be used to reference a remote prim, rather than the caller needing to know the key of the prim, and even what region it is in.

A name registration context is defined by the current script, the current prim, the region containing the prim, and the owner of the object.  The owner, prim and script are all implicit in the call, so only name to register is required, along with the desired scope for the registration.

Name registrations are always relative to an owner key, and could be either global to the entire grid, or local to the region where the prim is located, depending on the scope parameter. This can have one of the following values:

IW_SCOPE_REGION – name registered for return by queries in this region only
IW_SCOPE_GRID – name is registered grid-wide for any script that queries the name

Note: There can only be multiple instances of the registered name per user if IW_SCOPE_REGION is used (although still only one per region). This is because any new registration will replace the existing older registration.  A new IW_SCOPE_GRID registration will replace all IW_SCOPE_REGION registrations.

Important: There is no need to re-register at regular intervals, and normally it is not necessary to deregister a name.  The name registration will be removed if an object containing a name registration is deleted or leaves a region. However, if a prim enters a new region, the previous registration will be removed, therefore the script must register again in the new region, so that scripts that query the name can find it in the new location.  That said, if it is desirable to have a remaining prim no longer be found using iwGetRegistration, the script can call iwDeregisterName.

Examples:

The following examples demonstrate the basic usage of iwRegisterName:

iwRegisterName(“Danceball Rezzer”, IW_SCOPE_REGION);
or
iwRegisterName(“Upgrade Server”, IW_SCOPE_GRID);

iwDeregisterName(string name, integer scope)

This function removes the registration that corresponds to the specified name (alias) and scope.  Most scripts do not need to call this function.  It is provided to complete the needs of the script author.

Note: Name registrations will automatically be removed when the script context is no longer valid, i.e. if the object containing the script that registered the name is no longer in the region. This can happen if the object containing the script is deleted, or crosses into another region.

list iwGetRegistration(key owner, string name, integer scope)

This function queries a name registered using iwRegisterName.  Given the owner of the object in question, and the original name and scope, it will return a list containing the information needed for a call to iwMessageRemoteObject.

The following example queries the name registration information in the two example cases in iwRegisterName above:

key region;
key prim;
key script;
list info;
info = iwGetRegistration(llGetCreator(), 
           “Update Server”, IW_SCOPE_GRID);

or

info = iwGetRegistration(llGetOwner(),
           “Danceball Rezzer”, IW_SCOPE_REGION);

Note: The script can query registrations for other owners.  This is useful for a script querying an object of its creator, e.g. after an inventory transfer to another user.  The case above uses an update server as an example of this.

Here is an example of how to extract the information returned from iwGetRegistration:

key region;
key prim;
key script;
list info;
info = iwGetRegistration(llGetCreator(),
           “Update Server”, IW_SCOPE_GRID);
region = llList2Key(info, 0);
prim = llList2Key(info, 1);
script = llList2Key(info, 2);

These three values represent the script context of the name registration, and are the required context for iwMessageRemoteObject.

key iwGetRegistrationKey(key owner, string name, integer scope)

This function queries a name registered using iwRegisterName.  However, unlike iwGetRegistration, this function only returns the key of the prim of the registration context. This is a convenience function for users of iwMessageObject, since it is simpler than retrieving a list and extracting only the second list item.

Given the owner of the object in question, and the original name and scope, this function will return the object key needed for a call to iwMessageObject.

integer iwMessageObject(key object, string message)

This function sends an arbitrary string message to an object in the same region. To do so, the sending script must know the key of the destination prim to send the message to. The object key can come from any of the traditional sources, such as llRegionSayTo, listen events, collision events, sensor replies, etc. It may also come from a call to iwGetRegistration.  The parameters other than the message itself correspond to the three context values stored and returned by iwGetRegistration.

Here is a simple practical example:

key prim = iwGetRegistrationKey(
               llGetOwner(), “Danceball Rezzer”, IW_SCOPE_REGION);
iwMessageObject(prim, “The message.”);

integer iwMessageRemoteObject(key region, key object, key script, string message)

This function sends an arbitrary string message to an object, anywhere on the grid. To do so, the sending script must know the keys of the destination region, the destination prim, and optionally a script to send the message to in that destination prim.  The parameters other than the message itself correspond to the three context values stored and returned by iwGetRegistration.

Here is a simple practical example:

list info = iwGetRegistration(
              llGetCreator(), “Update Server”, IW_SCOPE_GRID);
key region = llList2Key(info, 0);
key prim = llList2Key(info, 1);
key script = llList2Key(info, 2);
iwMessageRemoteObject(gRegion, gPrim, gScript, message);

Note: The region parameter is optional and if NULL_KEY is passed, the current region is assumed.  Similarly, the script parameter is optional and if NULL_KEY is provided, the message will be sent to all scripts in the target prim.  This is less secure than specifying the target script, as it allows a “listener” script to be added to the receiver prim if the object is modifiable.

Important: The examples so far do not use it, however iwMessageRemoteObject provides a non-zero return code on error.  This allows for automatic recognition of errors on sends, and is a sign to query for an updated destination context.

Single-attempt retry logic for iwMessageRemoteObject is fairly simple:

list info = iwGetRegistration(
              llGetCreator(), “Update Server”, IW_SCOPE_GRID);
key region = llList2Key(info, 0);
key prim = llList2Key(info, 1);
key script = llList2Key(info, 2);
integer rc = 
      iwMessageRemoteObject(gRegion, gPrim, gScript, message);
if (rc != 0)
{
    llSleep(5.0);  // short delay before retrying
    info = iwGetRegistration(
             llGetCreator(), “Update Server”, IW_SCOPE_GRID);
    region = llList2Key(info, 0);
    prim = llList2Key(info, 1);
    script = llList2Key(info, 2);
    iwMessageRemoteObject(gRegion, gPrim, gScript, message);
}

It gets a bit more involved if the desire is to provide multiple retries, however the following example is a more production-ready example that includes error handling and retry logic:

// Sends a message to the prim registered with 'destName'.
// Returns TRUE if the message could be sent, FALSE otherwise.
integer SendMessage(string destName, string message)
{
  key region = NULL_KEY;
  key prim = NULL_KEY;
  key script = NULL_KEY;
  integer rc = -1;  // force it into the loop the first time
  integer retries = 5;

  while (rc != 0)
  {
    if (prim != NULL_KEY)
    { // if not the first time through the loop
      rc = iwMessageRemoteObject(region, prim, script, message);
      if (rc == 0)
      return TRUE;  // successfully sent
    }
    if (retries == 0)
      break;  // give up

    if (prim != NULL_KEY)  // message failed
    {
      // back off and wait for the registration to update
      retries--;
      llSleep(5.0); 
    }

    // we need to query the registration for
    // an updated (or initial) value
    list info = iwGetRegistration(
                  llGetOwner(), destName, IW_SCOPE_GRID);
    gRegion = llList2Key(info, 0);
    gPrim = llList2Key(info, 1);
    gScript = llList2Key(info, 2);
  }

  // retries exhausted
  return FALSE;
}

iwRegionShout(integer channel, string message)

The changes in this proposal include fixing local chat operations (e.g. user chat and llSay and its variants) to extend across region borders with the usual chat range rules.  However, sometimes it is useful for scripts to be able to communicate with other objects anywhere in a region, regardless of range. For this reason, llRegionSay was created.

This function extends llRegionSay to have a longer range, just as llShout extends llSay.  In this case, iwRegionShout will be a chat broadcast that can be heard by listeners in the region containing the script or any of its immediate neighbor regions.  In other respects, it has the same semantics as llRegionSay, including the restriction of not being able to use channel 0.

 

Conclusion

These proposed functions would fill a fairly large gap in functionality, and reduce the need for complex solutions developed externally.  It is hoped that these proposed functions might set a new standard for easy and reliable scripting of communications between in-world objects, making it more accessible to novice scripters and making networked products more reliable in InWorldz.

However, it is important to remember that they are subject to change at any time during implementation and may yet evolve before finally appearing on the main grid.  They are expected to be completed over the next week or two and available soon on the InWorldz beta grid.  They are expected to be available on the main grid sometime in the next two or three grid updates.