Memory Module

From Second Life Wiki
Jump to: navigation, search

Author: Created by Kira Komarov.

Statement: You are free to use this technique in all your creations as long as you pay explicit credits to the author.

License: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. The GPL license: [1]

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

KBtip2.png Tip: MISUNDERSTANDINGS As this page has received over 5000 visits, perhaps it should be pointed out that the whole motivation behind this script is false and based on misunderstanding. Global variables held in a script are NOT reset on rezzing. It is perfectly safe to hold values "across sessions" in global variables. They will continue to hold their latest values on re-rezzing, logout/in, detach/attach or SIM restart (unless the script specifically includes code within some event such as on_rez(), changed() or attach() to reset the script or some similar action.) However, it is possible for a script's variables to lose any changes since last rez/attach if there is a simulator malfunction (rezzed) or viewer crash (attachment). If variable values are mission critical and simply can not survive any possible reversion to stale data, then these techniques can be of value.

Motivation: One of the main problems with LSL scripting is that it doesn't have any state-persistency. An LSL script will not hold the state across sessions. That means if you are wearing an attachment and you have a script within that attachment which has some variables set to some user-defined value, once you log out and log-in back again, the variables won't hold the same values. Instead, the script will be re-initialized and the variables will have to be set again. This script will allow you to make your global variables persist across sessions by using some ingenuity dealing with prims and textures.

Usage Example: You are creating some attachment, for example, clothing which has some options the user can configure. However, if the user configures those options and you store the value of those options in a variable, once the user logs out, those global variables are reinitialised and they must set them again.

How it works: The script is placed in an invisible linked prim and listens for link messages. Depending on the format of the message it sets or retrieves a setting. It is not meant to hold a lot of data and it is limited by the length of a key. When a linked prim wants to store a string, it sends a link message to this invisible prim with the corresponding parameters. The invisible prim which contains this script, then converts the string to a key and sets the texture of one of its faces to the key to which the string was converted to. To retrieve the message, another link message is sent which retrieves the key off the corresponding face and converts it back to a string.


 * To store a message: llMessageLinked(<link number the memory script is in>, <face number>, <string to store>, "@push");
 * To retrieve a message: llMessageLinked(<link number the memory script is in>, <face number>, <string to store>, "@pull");

Intuitively, the invisible prim this script is in has any number of faces (in the syntax: <face number>) and thus allows you to store as many messages as there are faces on that particular prim. For example, if your invisible prim that this script is in is a simple 6-faced box, then you can store up to 6 messages: one for every face. In that case, if you would want to store a string on the memory location 3, the <face number> parameter above would be 3.

Case example: We create a simple flight assist device. We build two boxes, one for the flight script and one to hold this memory module and link them together. We want to allow the user to toggle the flight assist on and off but we would like to store that decision in a global variable and we want that variable to persist across sessions. In order to do so, when the flight assist script wants to store a string, for example "flight_on" in the memory module, it will send a link message to the memory module:

  llMessageLinked(2, 6, "flight_on", "@push");

this will make the memory module store the string "flight_on" in memory location 6. When the user logs out and logs in back again, the flight-assist must retrieve this setting from the memory module. It knows that it uses the 6th memory location (6th face). Thus, it asks the memory module for the string that was stored in memory location 6:

  llMessageLinked(2, 6, "flight_on", "@pull");

When the memory module receives the "@pull" request, it sends back a message to all linked prims containing the string that was stored in memory location 6. For example, a script could use this to set the global variable on state_entry():

integer flightOn = FALSE;
default {
  state_entry() {
    // The memory module has link number 2 and we want
    // to retrieve the contents of the memory location 6
    llMessageLinked(2, 6, "", "@pull");
  link_message(integer sender_num, integer num, string str, key id) {
    // The memory module sends a message to all
    // prims with the string parameter set to the
    // contents of the memory location that was 
    // queried with llMessageLinked() in state_entry()
    // and sets the value of a global variable.
    if(str == "flight_on") {
      flightOn = TRUE;

The following is the code of the memory module which must be in the invisible prim. It is not optimised and there is still room for improvement. One thing which should remain the same, or at most inlined, is the cSelect() function. It contains a lookup table of hex and ascii values which is declared locally instead of globally so that it will be declared on the fly without being constantly in memory.

// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see:     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
list cSelect(integer hex) {
	if(hex) return ["20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f","30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f","40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f","50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f","60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f","70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e"];
	return [" ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","[","\\"/*"/**/,"]","^","_","`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","{","|","}","~"]; 
string AsciiToKey(string str) {
    integer itra;
    string hexStr;
    for(itra=0; itra<llStringLength(str); itra++) {
        hexStr += llList2String(cSelect(1), llListFindList(cSelect(0), (list)llGetSubString(str, itra, itra)));
    while(itra<32) {
        hexStr += "0";
    return llGetSubString(hexStr,0,7) + "-" + llGetSubString(hexStr,8,11)+ "-" + llGetSubString(hexStr,12,15)+ "-" + llGetSubString(hexStr,16,19)+ "-" + llGetSubString(hexStr,20,31);
string KeyToAscii(key k) {
    integer itra;
    string strKey;
    for(itra=0; itra<llStringLength(k); itra++) {
        if(llGetSubString(k, itra, itra) != "-")
            strKey += llGetSubString(k, itra, itra);
    string pureKey;
    for(itra=0; itra<llStringLength(strKey); itra+=2) {
        if(llGetSubString(strKey, itra, itra+1) != "00")
            pureKey += llGetSubString(strKey, itra, itra+1);
    string asciiStr;
    for(itra=0; itra<llStringLength(pureKey); itra+=2) {
        asciiStr += llList2String(cSelect(0), llListFindList(cSelect(1), (list)llGetSubString(pureKey, itra, itra+1)));
    return asciiStr;
default {
    state_entry() {
        llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_BOX, 
                            PRIM_HOLE_DEFAULT,  // hole_shape
                            <0.00, 1.0, 0.0>,   // cut
                            0.0,                // hollow
                            <0.0, 0.0, 0.0>,    // twist
                            <1.0, 1.0, 0.0>,    // top_size
                            <0.0, 0.0, 0.0>,    // top_Shear
                            <0.01, 0.01, 0.01>, // size
                            <0.0, 0.0, 0.0>,
    link_message(integer sender_num, integer num, string str, key id) {
        if(num < 0 || num > 6 || llStringLength(str) > 16) return;
        if(id == "@push") {
    	    llSetTexture((key)AsciiToKey(str), num);
    	    jump stored;
    	llMessageLinked(LINK_ALL_OTHERS, num, KeyToAscii(llGetTexture(num)), "@pull");