Don't click here unless you want to be banned.

LSL Wiki : ExchangeScriptModule

HomePage :: PageIndex :: RecentChanges :: RecentlyCommented :: UserSettings :: You are crawl338.us.archive.org

Script Modules

This is not actually a protocol, but I think it fits more nicely in this category then in any other.

A module is a self-contained script that is designed to work with other modules within the same object. They are based upon Object-Oriented Programming's objects and classes. Implemented properly, they can be reused easily between different projects. Scripting this way allows the scripter to abstract away complexity inherant in large projects.

Each module exposes an 'interface' to other modules. This interface allows other modules to communicate with it.

Module Syntax

Each module always declares these global functions:

// ========== For method invocation ==========
string randomSeperator(integer len) {
    integer firstChar = (integer)llFrand(60) + 20;  // Range of printable chars = 0x20 to 0x7E
    if (len <= 1)
        return llUnescapeURL("%"+(string)firstChar);
    integer lastChar;
    do { // Last char must not equal first char.
        lastChar = (integer)llFrand(60) + 20; 
    } while (lastChar == firstChar);
    string ret = llUnescapeURL("%"+(string)firstChar);
    for (len -= 2; len > 0; --len)
        ret += llUnescapeURL("%" + (string)((integer)llFrand(60) + 20));
    return ret + llUnescapeURL("%"+(string)lastChar);
}

string listToString(list src) {
    string chars = (string) src; // Squashes all elements together.
    string seperator;
    do { // Find a seperator that's not in the list's string form
        seperator = randomSeperator(3); // so we dont kill data.
    } while (llSubStringIndex(chars, seperator) != -1);
    return seperator + llDumpList2String(src, seperator);
}

list stringToList(string src) { // First 3 chars is seperator.
    return llParseStringKeepNulls(llDeleteSubString(src, 0, 2),
        [llGetSubString(src, 0, 2)], []);
}

callMethod(integer identifyer, string methodName, list parameters) {
    llMessageLinked(LINK_THIS, identifyer, // ID only necessary for return value.
        listToString(parameters), methodName);
}

returnValue(integer identifyer, string methodName, list value) {
    callMethod(identifyer, methodName + "_ret", value);
}
// =============================================

A module's "methods," the interface it exposes to the rest of the system, are declared within its link_message event handler.

// Turner Module
default {
    link_message(integer s, integer call, string params, key methodName) {
        if (methodName == "turn") {
            list paramList = stringToList(params);
            string direction = llList2String(paramList, 0);
            float speed = (float) llList2String(paramList, 1);
            // Code goes here...
        }
    }
}

The key parameter contains the method's name. Method names can only contain alphanumeric characters. Using underscores and dollar signs is not good practice (they may be used for different purposes later). The string parameter contains the method's parameters as list encoded as a string. The seperator between each element is the first three characters of the string. It is randomly generated so that it does not corrupt any of the list data. The integer parameter is optional, it is used to return values to callers of the method.

To call another module's methods, a "stub function" is used. These are usually prefixed with m_:
m_turn(string direction, float speed) {
    callMethod(0, "turn", [direction, speed]);
}

Return Values

Return values are simply method calls with a "_ret" name suffix. For example, if the turn method returned a value to its caller, and the caller wanted the return value, the caller would declare a turn_ret method:

integer m_turn(string direction, float speed) {
    integer retID = (integer)llFrand(188412);
    callMethod(retID, "turn", [direction, speed]);
    return retID;
}

integer turnCall;
default {
    state_entry() {
        turnCall = m_turn("left", 1.0);
    }
    link_message(integer s, integer call, string params, key methodName) {
        if (methodName == "turn_ret" && call == turnCall) {
            list paramList = stringToList(params);
            integer successful = (integer) llList2String(paramList, 0);
            if (successful) {
                llOwnerSay("Yay");
            }
        }
    }
}

Referring back to our Turner module example, it could be rewriten to return a value like so:
// Turner Module
default {
    link_message(integer s, integer call, string params, key methodName) {
        if (methodName == "turn") {
            list paramList = stringToList(params);
            string direction = llList2String(paramList, 0);
            float speed = (float) llList2String(paramList, 1);
            // Code goes here...
            returnValue(call, methodName, [TRUE]);
        }
    }
}
I created a way to return values synchronously, but havent used it to a great extent.

Standard Signaling

A well-formed module will always define a moduleReady(string module) method that returns a boolean indicating to the rest of the system that the module has finished "booting" - initializing after a reset. A module will always call moduleReset(string module) when and only when it has been reset. Here's a template:

// Assuming method invocation funcs have been declared above

m_moduleReset(string moduleName) {
    callMethod(0, "moduleReset", [moduleName]);
}

integer reset = TRUE;
firstRun() {
    if (!reset)
        return;
    this = llGetScriptName();
    m_moduleReset(this);
    reset = FALSE;
}

string this;
default {
    state_entry() {
        firstRun();
    }
    link_message(integer c, integer call, string params, key methodName) {
        if (methodName == "moduleReady") {
            list paramList = stringToList(params);
            string moduleName = llList2String(paramList, 0);
            if (moduleName == this)
                returnValue(call, methodName, [TRUE]);
            // Assuming that module has finished starting up at this point.
        }
    }
}

m_moduleReset allows other modules to release handles that the calling module will re-register. For example, the LibraryChatCodec will release listen handles held by the module that was reset. m_moduleReady is a legacy method that is used in startup dependancy resolution (so a module relying on another for initialization can wait until the other module is initialized)

Exchange
There are 3 comments on this page. [Display comments/form]