User:Ravenhurst Xeno/RBase Database
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
RBase Database
These programs comprise RBase, an entirely LSL based database system. With an appropriate hashing function, it should be able to easily handle 25,000 entries and/or 1 MB of data.
The RBase components do nothing on their own but are designed to be a basic key/value database backend for
one or more client scripts. All the database component scripts and the client scripts are contained within a
prim and the client script(s) communicate with the database through llMessageLinked().
This wiki page has some basic information for using the LSLbase scripts. The bulk of the information on using the database system is contained in the comments in the scripts themselves.
License
RBase is released under the GNU Public License
Components
The complete RBase has several different components:
DB |
This is the core database module. It can be used standalone by a client script or ganged together through DBMeta. |
DBMeta |
DBMeta is a multiple DB script controller that lets a client script gang together more than one core DB script for expanded data storage. |
DBSort |
DBSort is the sorting module that lets the client sort the data of one or more core DB scripts. Sorting is slow, susceptible to memory overflow, and of limited use under DBMeta. But it is here anyway. |
DBTest |
Interactive testing module to test the other components. |
DBExample |
Demonstrates basic use of the DB core module and the DBMeta multiple DB controller. |
Sending RBase Commands
The client script(s) and the LSLbase component script(s) are placed in the same prim. The client communicates with the database components through link messages: <lsl>
llMessageLinked( LINK_THIS, MsgId, DBCommand, Caller );
</lsl>
LINK_THIS |
All components are in the same prim |
|||||||||
MsgId |
The client program can set this to an arbitrary value and responses from the database components will have the same message id. |
|||||||||
DBCommand |
The command to the database. It is of the form: Target|command[|operand][|operand...] Each part of the command text is separated from the next by the '|' character.
|
Constants | Flow Control | Script Library | Tutorials
Contents
RBase Responses
Responses from the database will come back as link messages. The MsgId will match the MsgId of the command for which it is the response. The key value will be a s string containing the name of the RBase component that is sending the response. The actual message will be of the form: To|db_response|data[|data...]
Constants | Flow Control | Script Library | Tutorials Contents
To |
This will match the Caller parameter in the original database command |
db_response |
The literal string 'db_response' |
data[|data....] |
All database commands that generate a response message will have one or more pieces of response data separated by '|' charaters |
Sample Script
This shows some of the basic usage of the LSLbase system.
<lsl>
// // RXBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file 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. // // 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // DBExample - This is an example DB client program // // This script shows examples of using the RXBase system. // It assumes a basic core module called 'DB' and sixteen // ganged modules named 'DB 0'....'DB F' controlled through // DBMeta. // // 28 Mar 08 Begin release version Ravenhurst Xeno //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
string gProductId = "Ravenhust Xeno RXBase Example"; integer gVersion = 2008032801;
integer gSetup = TRUE;
string gTo; // who the message is sent to string gFrom; integer gSenderPrim; integer gMsgId;
// // state parameters string gTarget; string gKey; string gValue;
// // These are related to taking, parsing and acting on command text // Commands can come from chat, notecards and (soon) link messages // string gOp; // command to act on list gOps; string gOperand; // data for gOp
//
// Misc. attributes
string gMe = "";
default {
state_entry() { gMe = llGetScriptName(); } touch_start( integer nbrDetected ) { llSay(0,"Adding some values directly to the DB module\n"); // // First we will interact directly with a single DB module // and add some values to it gTarget = "DB"; // // clear out any previous data llMessageLinked( LINK_THIS, 10, gTarget+"|db_clear", gMe ); // // add a few key/value pairs llMessageLinked( LINK_THIS, 11, gTarget+"|db_add|red|This is the RED key value", gMe ); llMessageLinked( LINK_THIS, 12, gTarget + "|db_add|asharp|This is the ASHARP key value", gMe ); llMessageLinked( LINK_THIS, 13, gTarget + "|db_add|green|This is the GREEN key value", gMe ); llMessageLinked( LINK_THIS, 14, gTarget + "|db_add|blue|This is the BLUE key value", gMe );
// // list the data in the module. Note: the DB module will llSay // the values it contains. llSay(0,"List of DB's contents before delete"); llMessageLinked( LINK_THIS, 15, gTarget + "|db_list", gMe ); llSleep(2.0); // // get rid of the odd man out llMessageLinked( LINK_THIS, 16, "DB|db_delete|asharp", gMe );
llSay(0,"\nList of DB's contents after delete"); llMessageLinked( LINK_THIS, 17, gTarget + "|db_list", gMe ); llSleep(2.0); // // now we will go through a similar process but using the // dbmeta controller gTarget = "DBMeta"; llSay(0,"\nStarting DBMeta controll. Adding Data via DBMeta"); llMessageLinked( LINK_THIS, 18, gTarget + "|db_clear", gMe ); llSleep(2.0); llMessageLinked( LINK_THIS, 19, gTarget + "|db_add|100|100 key value", gMe ); llMessageLinked( LINK_THIS, 20, gTarget + "|db_add|110|110 key value", gMe ); llMessageLinked( LINK_THIS, 21, gTarget + "|db_add|120|120 key value", gMe ); llMessageLinked( LINK_THIS, 22, gTarget + "|db_add|200|200 key value", gMe ); llMessageLinked( LINK_THIS, 23, gTarget + "|db_add|300|300 key value", gMe );
// // list all the contents llSay(0,"\nList of data via DBMeta"); llMessageLinked( LINK_THIS, 24, gTarget + "|db_list", gMe );
llSleep(5.0); // // list the contents of one of the buckets under DBMeta control llSay(0,"\nList of the DB 1 data bucket directly"); gTarget = "DB 1"; llMessageLinked( LINK_THIS, 25, gTarget + "|db_list", gMe ); llSleep( 5.0 ); state fetch; }
}
state fetch {
state_entry() { llSay(0,"\nFetching Data Values"); // // reteive from the single core db mudle gTarget = "DB"; llMessageLinked( LINK_THIS, 26, gTarget+"|db_fetch|blue", gMe ); // // retrieve a value from the DBMeta controlled buckets gTarget = "DBMeta"; llMessageLinked( LINK_THIS, 27, gTarget+"|db_fetch|200", gMe ); // // time for the fetches to return llSetTimerEvent( 5.0 ); } // // or the control can come from inside the linkset link_message(integer senderPrim, integer nbr, string message, key id) {
gOps = llParseString2List( message, ["|"], [] ); if( llList2String( gOps, 0 ) == gMe && llList2String( gOps, 1 ) == "db_response" ) { llSay( 0, "Fetch Response: " + "MsgId ["+(string)nbr+"] " + "Message ["+message+"] " + "From ["+(string)id+"] " ); } } timer() { state sortSingle; }
}
state sortSingle {
state_entry() { // // retreive them in sorted order. Notice this is sent to the // DBSort module with the single DB module as the target // of the sort llSay(0,"\nSingle Bucket Sort"); llMessageLinked( LINK_THIS, 28, "DBSort|db_sort|DB|key|ascend|0", gMe ); } // // or the control can come from inside the linkset link_message(integer senderPrim, integer nbr, string message, key id) {
gOps = llParseString2List( message, ["|"], [] ); if( llList2String( gOps, 0 ) == gMe && llList2String( gOps, 1 ) == "db_response" ) { if( llList2String( gOps, 2 ) == "sortdone" ) { llSay(0, "Single Sort Done"); llSleep(5.0); state sortMulti; } else { llSay( 0, "Single Sort Response: " + "MsgId ["+(string)nbr+"] " + "Message ["+message+"] " + "From ["+(string)id+"] " ); } } }
}
state sortMulti {
state_entry() {
llSay(0,"\nSort all buckets via DBMeta"); // // retreive them in sorted order. Notice this is sent to the // DBSort module with an invalid target as the sort target // DBMeta will take over and provide the sorted response llMessageLinked( LINK_THIS, 29, "DBMeta|db_sort|X|key|ascend|0", gMe );
} // llSay(0,"Huh .... "+gTarget+"|dblist" );//!!! // or the control can come from inside the linkset link_message(integer senderPrim, integer nbr, string message, key id) {
gOps = llParseString2List( message, ["|"], [] ); if( llList2String( gOps, 0 ) == gMe && llList2String( gOps, 1 ) == "db_response" ) { if( llList2String( gOps, 2 ) == "sortdone" ) { llSay(0, "Multi Sort Done"); } else { llSay( 0, "Multi Sort Response: " + "MsgId ["+(string)nbr+"] " + "Message ["+message+"] " + "From ["+(string)id+"] " ); } } }
}
</lsl>
DB
This is the core of the database system. It can be used standalone directly by a client script or through the DBMeta controller script
<lsl> // // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file 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. // // 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // DB - Core general database script. DB stores and retreives key/value // pairs for other scripts // // link message formats: // to|db_add|key|value // to|db_update|key|value // to|db_delete|key // to|db_list // to|db_keyexists|key // to|db_valexists|value // to|db_fetch|key // to|db_fetchndx|ndx // to|db_fetchall // to|db_entries // to|db_clear // to|db_maxentries|value // // to - string matching the name of this script or 'all' // db_* - literal command // key - string key. All keys are unique in the db lists // value - string value // // // db_add - add the key/value pair to the db. If the key // already exists, it is not added to the database // db_update - replace or add the key/value pair to the db. // if the key already exists, the value is updated // if the key doesn't exist, it is added as a new // key value pair. // db_delete - remove the key value pair specified by key // db_list - dump the db contents to stdout // db_keyexists - determine if the specified key exists // a link message response of: // to|db_response|[0|ndx] // is sent back to the calling script where 0 // means the key doesn't exist and a positive // value is the key's index in the db list (index // NOT offset) // To avoid response collision, the link_message // number will be set to whatever the calling // link message used. // db_valexists - does the specified value exist in the db // have an entry where they are currently not // departed. (see db_keyexists for response) // db_fetch - return the specified key's value. // to|db_response|[""|value] // is sent back to the calling script where an empty // value means the key doesn't exist (or the key // was added with an empty value. use db_keyexists // to differentiate). // To avoid response collision, the link_message // number will be set to whatever the calling // db_fetchndx - return the key and value in the specified // ndx location. Key/Value pairs are maintained // in the order they are added/updated. // db_fetchall - returns in succession all the key/value // pairs. The order returned is the ordered // they were added. // to|db_response|index|key|value // is sent back to the calling script. // db_fetchallsorted - returns in succession all the key/value // pairs. The order returned is lexigraphic // sort order of the keys. The response will be: // to|db_response|key|value // is sent back to the calling script. // To avoid response collision, the link_message // number will be set to whatever the calling // db_entries - returns the number of entries in the // database. // db_memory - returns the free memory low water mark // as tallied by llGetFreeMemory() // db_clear - resets and clears the database // db_ping - causes DB to return an empty response // db_status - prints basic version/status information // db_reset - restarts script // db_maxentries - limits the number of enties in the db to the value // amount. Defaults to 100. Entries added in excess // of the maximum will cause the oldest entries to be // silently dropped from the database. // if maxentries is set to less than 1, then no max // entry limit will be enforced and the DB script // will continue to accept new entries until it crashes // because of memory exhuastion // // History: // 07 Dec 07 Begin Ravenhurst Xeno // 23 Mar 08 Begin release version Ravenhurst Xeno // //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
//==========================================================// // // // Attribute Variables // // // //==========================================================//
string gProductId = "Ravenhust Xeno RBase DB Core";
integer gVersion = 2008032301;
string gCmdSepChar;
integer gSetup = TRUE;
// // signalling data string gTo; // who the message is sent to string gFrom; integer gSenderPrim; integer gMsgId;
//
// These are related to taking, parsing and acting on command text
// Commands can come from chat, notecards and (soon) link messages
//
string gOp; // command to act on
string gOperand; // data for gOp
//
// Misc. attributes
string gMe = "";
//
// History Specific Data
list gKeys;
list gValues;
string gKey;
string gValue;
integer gMaxEntries = 100;
//==========================================================// // // // General Utility Functions // // // //==========================================================//
//==========================================================// // // // Communications Funtions // // // //==========================================================//
//
// take the passed in message and break it into the command operation
// and its operand. The command is the first string upto whitespace. The
// operand is any text after the first white space. The operator
// goes in gOp and the rest of the message goes into gOperand
//
integer parseCommand( string message ) {
// // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; } // // parse command message = llStringTrim( message, STRING_TRIM ); ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gOp = message; gOperand = ""; } else { gOp = llGetSubString(message, 0 , ndx - 1 ); if( ndx + 1 >= llStringLength( message ) ) { gOperand = ""; } else { gOperand = llGetSubString(message, ndx + 1 , -1 ); } } //!!!!!llOwnerSay("parse op ["+gOp+"] rand ["+gOperand+"]");//!!!!! return(1);
}
//
// link commands are just normal commands prepended with the target's
// name (and blank space or |). The target goes into gTo, the operator
// into gOp and the rest into gOperand. If the link message was valid
// and for us, 1 is returned. Otherwise 0 is returned
integer parseLinkCommand( string message ) {
message = llStringTrim( message, STRING_TRIM ); // // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; }
// // pull off target ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { return(0); }
gTo = llGetSubString( message, 0 , ndx - 1 );
// // a message for us? if( gTo == gMe || gTo == "all" ) { // // process the rest message = llGetSubString( message, ndx + 1, -1 ); return( parseCommand( message ) ) ; } return(0);
}
// // display some summary status information
integer status() {
string msg = gProductId + ". v. "+(string) gVersion; msg += " Status: "; llSay(0, "["+gMe+"] "+msg ); llSay(0, "["+gMe+"] DB Entries: "+(string)llGetListLength(gKeys)); llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );
return(1);
}
//==========================================================// // // // Database Related Functions // // // //==========================================================//
//
// does the key exist in the db?
integer isInKeys( string k ) {
integer ndx = llListFindList( gKeys, [k] ); return(++ndx);
}
// // does the value exist in the db integer isInValues( string value ) {
integer ndx = llListFindList( gValues, [value] ); return(++ndx);
}
sendResponse( string payload ) {
if( payload != "" ) { payload = "|" + payload; }
llMessageLinked( gSenderPrim, gMsgId, gFrom+"|db_response"+payload, gMe );
}
// // if the database has grown past its max size, this function // trims it back down by removing the oldest entries first. integer fifoDB() {
if( gMaxEntries < 1 ) { return(0); }
integer nbr = llGetListLength( gKeys ); integer stop = (nbr - gMaxEntries); if( nbr > gMaxEntries ) { gKeys = llDeleteSubList( gKeys, 0, (stop - 1) ); gValues = llDeleteSubList( gValues, 0, (stop - 1 )); return(stop + 1); } return(0);
}
//
// processCommand. Commands
// Commands are accepted from chat and link messages.
// The command line must be parsed into gOp & gOperand by the parseCommand
// function before transitioning to this function.
processCommand() {
// // process command This is the msster switch // // first up, generic state controls if( gOp == "db_reset" ) { llSay(0,llGetObjectName()+"::"+gMe+" Reseting"); llResetScript(); } else if( gOp == "db_status" ) { status(); } // // db commands else if( gOp == "db_add" ) { parseCommand( gOperand ); gKey = gOp; gValue = gOperand;
if( ! isInKeys( gKey ) ) { gKeys = (gKeys = []) + gKeys + [gKey]; gValues = (gValues = []) + gValues + [gValue]; } fifoDB(); } else if( gOp == "db_update" ) {
parseCommand( gOperand );
integer ndx;
gKey = gOp; gValue = gOperand;
if( ( ndx = isInKeys( gKey ) ) ) { --ndx; gKeys = llDeleteSubList( gKeys, ndx, ndx ); gValues = llDeleteSubList( gValues, ndx, ndx ); }
gKeys = (gKeys = []) + gKeys + [gKey]; gValues = (gValues = []) + gValues + [gValue];
} else if( gOp == "db_delete" ) { integer ndx;
gKey = gOperand; // // don't delete non-existant keys (duh!) if( (ndx = isInKeys( gKey )) ) { --ndx; gKeys = llDeleteSubList( gKeys, ndx, ndx ); gValues = llDeleteSubList( gValues, ndx, ndx ); } } else if( gOp == "db_list" ) { integer nbr = llGetListLength( gKeys ); integer i;
for( i = 0 ; i < nbr ; ++i ) { llSay( 0, "::"+gMe+" "+ "["+(string)(i+1)+"] " + llList2String( gKeys, i ) + " - " + llList2String( gValues, i ) ); } } else if( gOp == "db_keyexists" ) { gKey = gOperand; sendResponse( (string)isInKeys( gKey ) );
} else if( gOp == "db_valexists" ) { gValue = gOperand; sendResponse( (string)isInValues( gValue ) ); } else if( gOp == "db_fetch" ) {
gKey = gOperand; integer ndx = isInKeys( gKey ); if( ndx ) { --ndx; sendResponse( llList2String( gValues, ndx ) ); } else { sendResponse( "" ); } } else if( gOp == "db_fetchndx" ) { integer ndx = (integer) gOperand; --ndx; // index -> offset if( ndx >= 0 && ndx < llGetListLength( gKeys ) ) { sendResponse( llList2String( gKeys,ndx ) + "|" + llList2String( gValues,ndx ) ); } else { sendResponse( "|" ); } } else if( gOp == "db_fetchall" ) { integer len = llGetListLength( gKeys ); integer i; for( i = 0 ; i < len ; ++i ) { sendResponse( (string)(i + 1) + "|" + llList2String( gKeys, i ) + "|" + llList2String( gValues, i ) ); // // output throttle if( i > 50 ) { llSleep(0.25); // don't overrun client } } llSleep(0.5); // token lag defense sendResponse("fetchalldone"); } else if( gOp == "db_entries" ) { sendResponse( (string)llGetListLength( gKeys ) ); } else if( gOp == "db_memory" ) { sendResponse( (string)llGetFreeMemory() ); } else if( gOp == "db_clear" ) { gKeys = []; gValues = []; } else if( gOp == "db_ping" ) { sendResponse(""); } else if( gOp == "db_maxentries" ) { gMaxEntries = (integer)gOperand; fifoDB(); } return;
}
//==========================================================// // // // States // // // //==========================================================//
default {
state_entry() { // // our one time setup is fairly basic if( gSetup ) { gSetup = FALSE; llSetRemoteScriptAccessPin( 0 ); gMe = llGetScriptName(); } } // // or the control can come from inside the linkset link_message(integer senderNbr, integer nbr, string message, key id) { gFrom = (string) id; gMsgId = nbr; gSenderPrim = senderNbr; if( parseLinkCommand( message ) ) { processCommand(); } } on_rez( integer param ) { llResetScript(); }
}
</lsl>
DB Meta
The DBMeta script is used to control multiple DB core scripts together to create an expanded storage space. Each core DB script can contain about 100 - 200 key/value pairs and/or about 10K of data (which ever limit is hit first). The DBMeta script, with an appropriate hashing function, should be able to control up to about 200 core DB scripts. This would give a client script a theoretical maximum storage of up to 40,000 key/value pairs and/or 2 MB of data.
<lsl>
// // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file 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. // // 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // // DBMeta - Meta DB controller. DBMeta is a meta controller for multiple // DB scripts. Using DBMeta with multiple DB scripts allows for // a greatly expanded database storage at the expense of a // few features of the native DB script. // // In this configuration, the client script doesn't communicate // directly with the back end DB script. Instead it sends its // commands to the DBMeta controller and it then relays the // command(s) to the appropriate DB storage scripts. Basically // DBMeta is a hashing switch and the DB storage scripts are the // hash buckets: // // Client Script<--+----------+ // | | | // V | | // DBMeta >----+ | // ^ | // | | // +----------+----------+--------+ | // | | | | | // v v v v | // DB1 DB2 DB3 DBx | // | | | | | // +----------+----------+--------+-----+ // // Any responses to the client script can come from either // the individual DB bucket script or from the DBMeta controller // depending on the client's command. // // By default, DBMeta is configured to accecpt UUID's as // a key value and hash on the first character of the UUID // into one of sixteen DB buckets. // // To change the hashing stragegy, rewrite the HashTag // function to generate whatever is an appropriate hash // value for the expected input keys. List all the valid // output hash tags of the HashTag function in the gBucketTags // list. Then install a DB script for each hash bucket tag // with the name gBucketScriptBase + Tag // // e.g. // // if your database will be storing avatar names, // change the HashTag function so it returns the first letter // of the avatars name. Set the varialbe gBucketTags to ba // a list [ "A", "B", .... , "Z" ]; .Then create 26 copies of // the DB script names DBA, DBB, DBC, .... , DBZ (assuming // gDBucketScriptBase == "DB"). // // For the most part, all the native DB commands operate // just the same through DBMeta. There are some that are // changed or not supported: // // db_maxentries - sets the max number of entries // each DB bucket can contain, not the // database as a whole // db_fetchndx - not supported. // // // // // // History: // 07 Feb 08 Begin Ravenhurst Xeno // Adapted from DB // 23 Mar 08 Begin release version Ravenhurst Xeno //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
//==========================================================// // // // Attribute Variables // // // //==========================================================//
string gProductId = "Ravenhust Xeno RBase DB Hasher";
integer gVersion = 2008032301;
string gCmdSepChar;
integer gSetup = TRUE;
string gTo; // who the message is sent to string gFrom; integer gMsgId; integer gSenderPrim;
//
// These are related to taking, parsing and acting on command text
// Commands can come from chat, notecards and (soon) link messages
//
string gOp; // command to act on
list gOps; // further enumeration
string gOperand; // data for gOp
//
// Misc. attributes
string gMe = "";
//
// Database specific values
string gKey;
string gValue;
string gTag;
string gProcessing;
string gProcessingFrom;
integer gProcessingSenderPrim;
integer gProcessingMsgId;
string gBucketScriptBase = "DB "; // trailing space is significant
integer gBucketScriptBaseLen;
integer gSorting = FALSE;
integer gFetching = FALSE;
string gFetchFrom;
integer gFetchSenderPrim;
integer gFetchMsgId;
integer gFetchBucket = 0;
integer gSortBucket = 0;
string gSortOn = "val";
string gSortDirection = "ascend";
string gSortBite = "-1";
string gSortingFrom;
integer gSortingSenderPrim;
integer gSortingMsgId;
// // change this so there is an entry matching each possible return value // of HashTag
list gBucketTags = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ];
list gBucketResponses;
//==========================================================// // // // Database Functions // // // //==========================================================//
// // this is a fairly generic first charactger hash usuable for // several different basic hashing stratagies just by changing // the available hash buckets
string HashTag( string str ) {
// // change this strategy to suit your needs string s = llToUpper( llGetSubString( str, 0, 0 ) ); if( llListFindList( gBucketTags, [ s ] ) >= 0 ) { return( s ); } else { return( llList2String( gBucketTags, 0 ) ); }
}
// // StartProcessing, FinishProcessing, AddResponse - these are used // when we will sit between the client and the buckets for the entire // transaction and we will respond to the client rather than the // DB bucket itself. This happens when we need to get information // from all the buckets and then refactor that data for the client. // So we send the command to all the buckets, get all their responses // and then send our response to the client.
StartProcessing( string cmd ) {
integer i; integer len = llGetListLength( gBucketTags );
// // only process one at a time if( gProcessing != "" ) { return; }
// // clear our response accumulator gBucketResponses = []; for( i = 0 ; i < len ; ++i ) { gBucketResponses = ( gBucketResponses = [] ) + gBucketResponses + [ "" ]; }
// // backtrace parseCommand( cmd ); gProcessing = gOp; gProcessingFrom = gFrom; gProcessingSenderPrim = gSenderPrim; gProcessingMsgId = gMsgId;
// // don't wait indefinetly llSetTimerEvent( len + 10 ); SendToAllBuckets( cmd ); return;
}
AddResponse( string from, string response ) {
from = llGetSubString( from, gBucketScriptBaseLen, -1 ); integer ndx = llListFindList( gBucketTags, [from] ); integer len = llGetListLength( gBucketTags ); if( ndx < 0 ) { return; }
gBucketResponses = llDeleteSubList( gBucketResponses, ndx, ndx ); gBucketResponses = llListInsertList( gBucketResponses, [response], ndx ); // // have all the buckets had their say? for( ndx = 0 ; ndx < len ; ++ndx ) { if( llList2String( gBucketResponses, ndx ) == "" ) { return; } } FinishProcessing();
}
FinishProcessing() {
integer i; integer len = llGetListLength( gBucketTags ); integer val;
llSetTimerEvent(0); if( gProcessing == "" ) { return; }
if( gProcessing == "db_valexists" ) { val = 0; i = 0; while( i < len && ! val ) { val = (integer)llList2String( gBucketResponses, i ); ++i; } llMessageLinked( LINK_THIS, gProcessingMsgId, gProcessingFrom+"|db_response|"+(string)val, gMe ); } else if( gProcessing == "db_entries" ) { val = 0; for( i = 0 ; i < len ; i++ ) { val += (integer)llList2String( gBucketResponses, i ); } llMessageLinked( LINK_THIS, gProcessingMsgId, gProcessingFrom+"|db_response|"+(string)val, gMe ); }
ResetProcessing();
}
ResetProcessing() {
llSetTimerEvent(0); gProcessing = ""; gProcessingFrom = ""; gProcessingSenderPrim = -1; gProcessingMsgId = -1;
}
//==========================================================// // // // Communications Functions // // // //==========================================================//
// // There are two sets of messages to the bucket scripts: Send & Proxy // // Send is the normal send a message to them and we get any return // message from the bucket script. The proxy messages are sent on // behalf of our caller and any responses from the bucket script // go directly back to our caller instead of us
ProxyToAllBuckets( string payload ) {
integer i; integer len = llGetListLength( gBucketTags );
for( i = 0 ; i < len ; i++ ) { gTag = llList2String( gBucketTags, i ); ProxyToBucket( payload ); llSleep(0.2); }
}
ProxyToBucket( string payload ) {
llMessageLinked( LINK_THIS, gMsgId, gBucketScriptBase+gTag+"|"+payload, gFrom );
}
SendToAllBuckets( string payload ) {
integer i; integer len = llGetListLength( gBucketTags );
for( i = 0 ; i < len ; i++ ) { gTag = llList2String( gBucketTags, i ); SendToBucket( payload ); }
}
SendToBucket( string payload ) {
llMessageLinked( LINK_THIS, gMsgId, gBucketScriptBase+gTag+"|"+payload, gMe );
}
SortNextBucket() {
// // Done sorting yet? if( gSortBucket >= llGetListLength( gBucketTags ) ) { gSorting = FALSE; llSetTimerEvent(0.0); llMessageLinked( gSortingSenderPrim, gSortingMsgId, gSortingFrom+"|db_response|sortdone", gMe ); return; } // // not done, start the sort of the next bucket. // responses are funnelled through us only so we know when // the bucket has finished sorting gTag = llList2String( gBucketTags, gSortBucket ); gSortBucket++; string payload = "DBSort|db_sort" + "|"+gBucketScriptBase+gTag + "|"+gSortOn + "|"+gSortDirection + "|"+gSortBite; llMessageLinked( LINK_THIS, gMsgId, payload, gMe ); llSetTimerEvent( 60.0 ); // give sorter time to do its work
}
// // Similar to the sort, we funnel all the fetchs through us. This // isn't strictly neccessary but it helps keep the data to the client // in bucket order and means the client will only see one fetchalldone // signal.
FetchAllNext() {
// // Done fetching yet? if( gFetchBucket >= llGetListLength( gBucketTags ) ) { gFetching = FALSE; llSetTimerEvent(0.0); llMessageLinked( gFetchSenderPrim, gFetchMsgId, gFetchFrom+"|db_response|fetchalldone", gMe ); return; } // // not done, start the fetch of the next bucket. // responses are funnelled through us only so we know when // the bucket has finished fetching gTag = llList2String( gBucketTags, gFetchBucket ); gFetchBucket++; llMessageLinked( LINK_THIS, gMsgId, gBucketScriptBase+gTag+"|db_fetchall", gMe ); llSetTimerEvent( 5.0 );
}
//
// take the passed in message and break it into the command operation
// and its operand. The command is the first string upto whitespace. The
// // take the passed in message and break it into the command operation // and its operand. The command is the first string upto whitespace. The // operand is any text after the first white space. The operator // goes in gOp and the rest of the message goes into gOperand // integer parseCommand( string message ) {
// // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; }
// // parse command message = llStringTrim( message, STRING_TRIM ); ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gOp = message; gOperand = ""; } else { gOp = llGetSubString(message, 0 , ndx - 1 ); gOperand = llGetSubString(message, ndx + 1 , -1 ); } return(TRUE);
}
//
// link commands are just normal commands prepended with the target's
// name (and blank space or |). The target goes into gTo, the operator
// into gOp and the rest into gOperand. If the link message was valid
// and for us, 1 is returned. Otherwise 0 is returned
integer parseLinkCommand( string message ) {
message = llStringTrim( message, STRING_TRIM ); // // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; } // // pull off target ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { return(FALSE); }
gTo = llGetSubString( message, 0 , ndx - 1 );
// // a message for us? if( gTo == gMe || gTo == "all" ) { // // process the rest message = llGetSubString( message, ndx + 1, -1 ); return( parseCommand( message ) ) ; } return(FALSE);
}
// // display some summary status information
integer status() {
string msg = gProductId + " version "+(string) gVersion; msg += " Status: "; llSay(0, msg ); llSay(0," Free Memory : "+(string)llGetFreeMemory() ); ProxyToAllBuckets( "db_status" );
return(TRUE);
}
//==========================================================// // // // Database Related Functions // // // //==========================================================//
//
// processCommand. Commands
// Commands are accepted from chat and link messages.
// The command line must be parsed into gOp & gOperand by the parseCommand
// function before transitioning to this function.
processCommand() {
// // process command This is the msster switch // // first up, generic state controls if( gOp == "db_reset" ) { llSay(0,llGetObjectName()+"::"+gMe+" Reseting"); SendToAllBuckets( "db_reset" ); llResetScript(); } else if( gOp == "db_status" ) { status(); } // // db commands else if( gOp == "db_response" ) { if( gSorting ) { if( gOperand == "sortdone" ) { llSetTimerEvent(0.0); SortNextBucket(); } else { llMessageLinked( LINK_THIS, gSortingMsgId, gSortingFrom+"|"+gOp+"|"+gOperand, gMe ); llSetTimerEvent(3.0); } } else if( gFetching ) { if( gOperand == "fetchalldone" ) { llSetTimerEvent(0.0); FetchAllNext(); } else { llMessageLinked( LINK_THIS, gFetchMsgId, gFetchFrom+"|"+gOp+"|"+gOperand, gMe ); llSetTimerEvent(3.0); } } else if( gProcessing != "" ) { AddResponse( gFrom, gOperand ); } } else if( gOp == "db_add" ) { parseCommand( gOperand ); gKey = gOp; gValue = gOperand; gTag = HashTag( gKey ); ProxyToBucket( "db_add|"+gKey+"|"+gValue );
} else if( gOp == "db_delete" ) { gKey = gOperand; gTag = HashTag( gKey ); ProxyToBucket( gOp+"|"+gKey );
} else if( gOp == "db_list" ) { ProxyToAllBuckets( "db_list" ); } else if( gOp == "db_keyexists" ) { gKey = gOperand; gTag = HashTag( gKey ); ProxyToBucket( gOp+"|"+gKey );
} else if( gOp == "db_valexists" ) { StartProcessing( "db_valexists|"+gOperand); } else if( gOp == "db_fetch" ) { gKey = gOperand; gTag = HashTag( gKey ); ProxyToBucket( gOp + "|" + gOperand ); } else if( gOp == "db_fetchall" ) {
// // we will intermediary this gFetching = TRUE; gFetchBucket = 0; gFetchFrom = gFrom; gFetchSenderPrim = gSenderPrim; gFetchMsgId = gMsgId; FetchAllNext();
} else if( gOp == "db_entries" ) { StartProcessing( "db_entries" ); } else if( gOp == "db_memory" ) { ProxyToAllBuckets( gOp ); } else if( gOp == "db_clear" ) { ProxyToAllBuckets( gOp ); } else if( gOp == "db_sort" ) { gOps = llParseString2List( gOperand, ["|"], [] ); gSorting = TRUE; gSortBucket = 0; gSortingFrom = gFrom; gSortingSenderPrim = gSenderPrim; gSortingMsgId = gMsgId; gSortOn = llList2String( gOps, 1); gSortDirection = llList2String( gOps, 2); gSortBite = llList2String( gOps, 3); SortNextBucket(); } return;
}
//==========================================================// // // // States // // // //==========================================================//
// // default. Since the particle system is set it and forget it, the // default state's primary responsiblity is to handle communications. // Command input can come from chat, notecards and from other linked // prims. //
default {
state_entry() { // // Startup processing is pretty minimal if( gSetup ) { gSetup = FALSE; llSetRemoteScriptAccessPin( 0 ); gMe = llGetScriptName(); gBucketScriptBaseLen = llStringLength( gBucketScriptBase ); } } // // or the control can come from inside the linkset link_message(integer senderPrim, integer nbr, string message, key id) { gFrom = (string) id; gMsgId = nbr; gSenderPrim = senderPrim; if( parseLinkCommand( message ) ) { processCommand(); } }
timer() {
// // ending up here usually means a processing failure. // Either a dead hash bucket script or really bad script lag if( gSorting ) { llOwnerSay(gMe+" Sorting Timeout <<<<<< DB Tag ["+gTag+"]"); SortNextBucket(); } else if( gFetching ) { llOwnerSay(gMe+" Fetch Timeout <<<<<< DB Tag ["+gTag+"]"); FetchAllNext(); } else if( gProcessing != "" ) { llOwnerSay( gMe + " Process Timout:"+gProcessing+" <<<<< " + "DB Tag [" + gTag +"]" ); ResetProcessing(); }
} on_rez( integer param ) { llResetScript(); }
}
</lsl>
DBSort
DBSort is a sorting module that can return the sorted contents of a single DB core module if called directly by the client script. Or if called via DBMeta, can return the sorted contents of all the DB core modules controlled by the DBMeta script. Sorry, each DB module is sorted individually. The database isn't sorted as a whole through DBMeta
<lsl>
// // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file 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. // // 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // DBSort - DB module to sort the contents of a DB script and return // it to the caller in sorted order // // link message formats: // to|db_sort|db_script|key_or_val|direction|bite // // to - string matching the name of this script or 'all' // db_sort - literal command // db_script - name of the DB script containing the data to be // sorted. // key_or_val - one of the literals 'key' or 'val' to indicate // sorting on the data key or the data value // direction - one of the literals 'ascend' or 'descend' to // indicate sort in ascending order or descending order // bite - how much of the key or value to compare. If // bite is 0 or less, the whole value or key will // be compared. // Because sorting requires more memory than just // storing data, it is possible to crash a DBSort // script through memory exhaustion. Using a sensible // bite value reduces the amount of memory needed by // DBSort. // // History: // 18 Feb 08 Begin Ravenhurst Xeno // Adapted from DB // 23 Mar 08 Begin release version Ravenhurst Xeno //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
//==========================================================// // // // Attribute Variables // // // //==========================================================//
string gProductId = "Ravenhust Xeno RBase Sorter";
integer gVersion = 2008032301;
string gCmdSepChar;
integer gSetup = TRUE;
string gTo; // who the message is sent to string gFrom; integer gSenderPrim; integer gMsgId;
//
// These are related to taking, parsing and acting on command text
// Commands can come from chat, notecards and (soon) link messages
//
string gOp; // command to act on
list gOps;
string gOperand; // data for gOp
//
// Misc. attributes
string gMe = "";
//
// Sort Specific Data
list gSorts;
string gDB;
integer gSortOn = 0; // 0 = key, 1 = value
integer gDirection = 0; // 0 = ascend, 1 = descend;
integer gBite = 0;
integer gIndex = 0;
string gData;
string gSortFrom;
integer gSortFromPrim;
integer gSortFromMsgId;
//==========================================================// // // // Communications Funtions // // // //==========================================================//
//
// take the passed in message and break it into the command operation
// and its operand. The command is the first string upto whitespace. The
// operand is any text after the first white space. The operator
// goes in gOp and the rest of the message goes into gOperand
//
integer parseCommand( string message ) {
// // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; }
// // parse command message = llStringTrim( message, STRING_TRIM ); ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gOp = message; gOperand = ""; } else { gOp = llGetSubString(message, 0 , ndx - 1 ); if( ndx + 1 >= llStringLength( message ) ) { gOperand = ""; } else { gOperand = llGetSubString(message, ndx + 1 , -1 ); } } return(1);
}
//
// link commands are just normal commands prepended with the target's
// name (and blank space). The target goes into gTo, the operator
// into gOp and the rest into gOperand. If the link message was valid
// and for us, 1 is returned. Otherwise 0 is returned
integer parseLinkCommand( string message ) {
message = llStringTrim( message, STRING_TRIM ); // // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; }
// // pull off target ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { return(0); }
gTo = llGetSubString( message, 0 , ndx - 1 );
// // a message for us? if( gTo == gMe || gTo == "all" ) { // // process the rest message = llGetSubString( message, ndx + 1, -1 ); return( parseCommand( message ) ) ; } return(0);
}
// // display some summary status information
integer status() {
string msg = gProductId + " version "+(string) gVersion; msg += " Status: "; llSay(0, "["+gMe+"] "+msg ); llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );
return(1);
}
//==========================================================// // // // Database Related Functions // // // //==========================================================//
DoSort() {
integer ndx; integer i; integer len = gIndex * 2;
// // goes off when we are done receiving responses to be sorted // sort our abbreviated version of the data and then have // the db module return gSorts = llListSort( gSorts, 2 , gDirection ); for( i = 1 ; i < len ; i += 2 ) { ndx = (integer)llList2String( gSorts, i ); llMessageLinked( gSortFromPrim, gSortFromMsgId, gDB+"|db_fetchndx|"+(string)ndx, gSortFrom );
llSleep(0.2); // don't overrun the db module
} // // give the db module time to finish responding llSleep(0.5); // // end of sort llMessageLinked( gSortFromPrim, gSortFromMsgId, gSortFrom+"|db_response|sortdone", gMe );
gDB = ""; gSorts = []; gSortFrom = "";
}
//
// processCommand. Commands
// Commands are accepted from chat and link messages.
// The command line must be parsed into gOp & gOperand by the parseCommand
// function before transitioning to this function.
processCommand() {
// // process command This is the msster switch // // first up, generic state controls if( gOp == "db_reset" ) { llSay(0,llGetObjectName()+"::"+gMe+" Reseting"); llResetScript(); } else if( gOp == "db_status" ) { status(); } else if( gOp == "db_sort" ) {
// // !!! might want to start by finding out how many entries // !!! there are first and make adjustments based on that? gSortFrom = gFrom; gSortFromPrim = gSenderPrim; gSortFromMsgId = gMsgId;
gOps = llParseString2List( gOperand, ["|"], [] ); gDB = llList2String( gOps, 0 ); if(llToLower( llList2String( gOps, 1 ) ) == "val" ) { gSortOn = 1; } else { gSortOn = 0; }
if( llToLower( llList2String( gOps, 2 ) ) == "descend" ) { gDirection = 0; } else { gDirection = 1; } gBite = (integer) llList2String( gOps, 3 ); if( gBite < 1 ) { gBite = -1; } gIndex = 0; gOps = []; llMessageLinked( LINK_THIS, 101, gDB+"|" + "db_fetchall", gMe ); llSetTimerEvent( 5 ); } else if( gOp == "db_response" ) {
if( gOperand == "fetchalldone" ) { // // got all the data to sort llSetTimerEvent(0); DoSort(); } else { // // data to be accumulated, // break apart gOps = llParseString2List( gOperand, ["|"], [] ); // // get the basic sorting data item gData = llList2String( gOps, gSortOn + 1); // // trim it down to size? if( gBite > 0 && llStringLength( gData ) > ( gBite * 2 ) ) { gData = llGetSubString( gData, 0, gBite ) + llGetSubString( gData, -gBite, -1 ); } // // add it to the sort list gSorts = (gSorts = []) + gSorts + [ gData, llList2String( gOps, 0) ]; gIndex++; llSetTimerEvent( 5 ); } } return;
}
//==========================================================// // // // States // // // //==========================================================//
// // default. Since the particle system is set it and forget it, the // default state's primary responsiblity is to handle communications. // Command input can come from chat, notecards and from other linked // prims. //
default {
state_entry() { // // on startups, try to read the default configuration notecard // (if it exists) if( gSetup ) { gSetup = FALSE; llSetRemoteScriptAccessPin( 0 ); gMe = llGetScriptName(); } } // // or the control can come from inside the linkset link_message(integer senderNbr, integer nbr, string message, key id) { gFrom = (string) id; gMsgId = nbr; gSenderPrim = senderNbr; if( parseLinkCommand( message ) ) { processCommand(); } } on_rez( integer param ) { llResetScript(); }
timer() {
llSetTimerEvent(0); DoSort();
}
}
</lsl>
DBTest
This is an interactive test module for the database system. It is not needed for normal operation of the database and is included for anyone that wants to hack at the operational components. It is not well commented, but if you are motivated enough to be hacking the modules, everything in here should be easy enough to figure out.
<lsl>
// // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file 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. // // 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // // DBTest - to interactivly test the SLbase modules // // History: // 10 Dec 07 Begin Ravenhurst Xeno // 23 Mar 08 Begin release version Ravenhurst Xeno // //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
//==========================================================// // // // Attribute Variables // // // //==========================================================//
string gProductId = "Ravenhust Xeno RBase DB Module Tester";
integer gVersion = 2008032301;
integer gCommandLd;
integer gCommandChannel = 10;
string gCmdSepChar;
integer gSetup = TRUE;
string gTo; // who the message is sent to key gAvKey; string gAvName; integer gMsgId; integer gSenderNbr;
//
// These are related to taking, parsing and acting on command text
// Commands can come from chat, notecards and (soon) link messages
//
list gOps; // holding space while parsing
string gOp; // command to act on
string gOperand; // data for gOp
//
// Misc. attributes
string gMe = "";
//
// Used by the card reader states for inter-comm
//
key gQueryId;
//
// test values
integer gLastTestNbr = 0;
list gTests;
list gDeletedTests;
string gTestKey;
string gTestValue;
integer gTestNbr;
string gTarget = "DB";
string gSortCmd; integer gSortBite = 4;
integer gSaltBase; list gSaltChars = [
"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" ]; //==========================================================// // // // Communications Funtions // // // //==========================================================//
// // commReset - // commReset's main purpose is to reset the communication channels. // commReset() {
// reset listener llListenRemove( gCommandLd ); gCommandLd = llListen( gCommandChannel, "", NULL_KEY, "" );
return;
}
//
// take the passed in message and break it into the command operation
// and its operand. The command is the first string upto whitespace. The
// operand is any text after the first white space. The operator
// goes in gOp and the rest of the message goes into gOperand
//
integer parseCommand( string message ) {
// // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; }
// // parse command message = llStringTrim( message, STRING_TRIM ); ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gOp = message; gOperand = ""; } else { gOp = llGetSubString(message, 0 , ndx - 1 ); gOperand = llGetSubString(message, ndx + 1 , -1 ); } return(1);
}
//
// link commands are just normal commands prepended with the target's
// name (and blank space). The target goes into gTo, the operator
// into gOp and the rest into gOperand. If the link message was valid
// and for us, 1 is returned. Otherwise 0 is returned
integer parseLinkCommand( string message ) {
message = llStringTrim( message, STRING_TRIM ); // // backwards compatibility gCmdSepChar = "|"; integer ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { gCmdSepChar = " "; }
// // pull off target ndx = llSubStringIndex( message, gCmdSepChar ); if( ndx < 0 ) { return(0); }
gTo = llGetSubString( message, 0 , ndx - 1 ); // // a message for us? if( gTo == gMe || gTo == "all" ) { // // process the rest message = llGetSubString( message, ndx + 1, -1 ); return( parseCommand( message ) ) ; } return(0);
}
// // display some summary status information
integer status() {
string msg = gProductId + ". v. "+(string) gVersion; msg += " Status: "; llSay( 0, msg ); llSay( 0, " DB_TEST Free Memory : "+(string)llGetFreeMemory() );
return(1);
}
//==========================================================// // // // Monitor History Related Functions // // // //==========================================================//
string SaltString( integer salt, integer len ) {
integer saltLen = llGetListLength( gSaltChars ); integer i; string resp = ""; integer ndx; for( i = 0 ; i < len ; i++ ) { ndx = (ndx + 1) * (salt + 1) * gSaltBase * 17; ndx %= saltLen; resp += llList2String( gSaltChars, ndx ); } return( resp);
}
// // convert integer to 4 digit hex string string Int2Hex( integer nbr ) {
integer mod; integer tmp; string str; if( nbr >= 4096 ) { tmp = nbr / 4096; mod = tmp % 16; str = llList2String( gSaltChars, mod ); nbr -= ( tmp * 4096 ); } if( nbr >= 256 ) { tmp = nbr / 256; mod = tmp % 16; str += llList2String( gSaltChars, mod ); nbr -= ( tmp * 256 ); } if( nbr >= 16 ) { tmp = nbr / 16; mod = tmp % 16; str += llList2String( gSaltChars, mod ); nbr -= ( tmp * 16 ); } mod = nbr % 16; str += llList2String( gSaltChars, mod ); return( str );
}
makeTest( integer nbr ) {
gTestNbr = nbr; gTestKey = (key) Int2Hex( nbr ); gTestValue = SaltString( gTestNbr, 5 ) + " J. Random Value " + Int2Hex( nbr ) + " (" + (string)nbr +")";
}
getRandomTest() {
integer len = llGetListLength( gTests ); integer ndx = (integer) llFrand( (float) len ); gTestNbr = llList2Integer( gTests, ndx ); makeTest( gTestNbr ); llSay(0, "DB_TEST Grabbed random test [" + (string)gTestKey+"] [" + gTestValue+"]" );
}
getRandomDeletedTest() {
integer len = llGetListLength( gDeletedTests ); integer ndx = (integer) llFrand( (float) len ); gTestNbr = llList2Integer( gDeletedTests, ndx ); makeTest( gTestNbr ); llSay(0, "DB_TEST Grabbed deleted test [" + (string)gTestKey+"] [" + gTestValue+"]" );
}
addTest( integer nbr ) {
gTests = ( gTests = [] ) + gTests + [nbr];
}
integer isTest( integer nbr ) {
integer ndx;
ndx = llListFindList( gTests, [nbr] ); return( ndx + 1 );
}
delTest( integer nbr ) {
integer ndx = isTest( nbr ); if( ndx ) { --ndx; gTests = llDeleteSubList( gTests, ndx, ndx ); gDeletedTests = (gDeletedTests = []) + gDeletedTests + [nbr]; }
}
dumpTests() {
string msg; integer len = llGetListLength( gTests ); integer i;
for( i = 0; i < len ; i++ ) { msg += llList2String( gTests, i ) + " "; } llSay(0,"DB_TEST Test Avs: "+msg );
len = llGetListLength( gDeletedTests );
msg = ""; for( i = 0; i < len ; i++ ) { msg += llList2String( gDeletedTests, i ) + " "; } llSay(0,"DB_TEST Deleted Test Avs: "+msg );
}
integer dumpStringList( list l ) {
integer items; integer ndx; items = llGetListLength( l ); for( ndx = 0 ; ndx < items ; ndx++ ) { llOwnerSay(" ["+(string)ndx+"] ["+llList2String( l, ndx )+"]"); } return(1);
}
//
// processCommand. Commands
// Commands are accepted from chat and link messages.
// The command line must be parsed into gOp & gOperand by the parseCommand
// function before transitioning to this function.
processCommand() {
// // first up, generic state controls if( gOp == "reset" ) { llSay(0,llGetObjectName()+"::"+gMe+" Reseting"); llResetScript(); } else if( gOp == "misc" ) { llDialog( gAvKey, "Various Stuff", [ "status", "memory", "entries", "clear" ], gCommandChannel ); } else if( gOp == "status" ) { llMessageLinked( LINK_THIS, 1, gTarget+"|" + "db_status", gMe ); status(); } else if( gOp == "add" ) { llDialog( gAvKey, "How many to add", [ "add_1", "add_2", "add_5", "add_10", "add_25", "add_50", "add_100" ], gCommandChannel ); } else if( gOp == "add_1" || gOp == "add_2" || gOp == "add_5" || gOp == "add_10" || gOp == "add_25" || gOp == "add_50" || gOp == "add_100" ) { integer nbr = (integer)llDeleteSubString( gOp, 0, 3 ); integer i; for( i = 0 ; i < nbr ; ++i ) { makeTest( gLastTestNbr ); ++gLastTestNbr; llSay(0,"DB_TEST Adding ["+(string)gTestKey+"] ["+gTestValue+"]"); llMessageLinked( LINK_THIS, 2, gTarget+"|" + "db_add|" + gTestKey+"|" + gTestValue, gMe ); addTest(gTestNbr); llSleep(0.15); } } else if( gOp == "update" ) { llDialog( gAvKey, "Update Type", [ "up_new", "up_exist" ], gCommandChannel ); } else if( gOp == "up_new") { makeTest( gLastTestNbr ); ++gLastTestNbr; llSay(0,"DB_TEST Update/Add ["+(string)gTestKey+"] ["+gTestValue+"]"); llMessageLinked( LINK_THIS, 2, gTarget+"|" + "db_update|" + gTestKey+"|" + gTestValue, gMe ); addTest(gTestNbr); } else if( gOp == "up_exist") { getRandomTest(); gTestValue = "Updated "+gTestValue; llSay(0,"DB_TEST Updating ["+(string)gTestKey+"] ["+gTestValue+"]"); llMessageLinked( LINK_THIS, 2, gTarget+"|" + "db_update|" + gTestKey+"|" + gTestValue, gMe ); } else if( gOp == "delete" ) { llDialog( gAvKey, "Delete Type", [ "d_val_key", "d_bad_key" ], gCommandChannel ); } else if( gOp == "d_val_key" ) { getRandomTest(); llMessageLinked( LINK_THIS, 3, gTarget+"|" + "db_delete|" + (string)gTestKey, gMe ); delTest( gTestNbr ); } else if( gOp == "d_bad_key" ) { makeTest( - 1 ); llMessageLinked( LINK_THIS, 4, gTarget+"|" + "db_delete|" + (string)gTestKey, gMe ); } else if( gOp == "list" ) { llMessageLinked( LINK_THIS, 5, gTarget+"|" + "db_list", gMe ); } else if( gOp == "mode" ) { llDialog( gAvKey, "Current Test Target "+gTarget, [ "mode_simple", "mode_meta", "mode_DB0", "mode_DB1", "mode_DB2", "mode_DB3", "mode_DB4", "mode_DB5", "mode_DB6", "mode_DBA", "mode_DBC", "mode_DBF" ], gCommandChannel ); } else if( gOp == "mode_simple" ) { gTarget = "DB"; } else if( gOp == "mode_meta" ) { gTarget = "DBMeta"; } else if( gOp == "mode_DB0" ){ gTarget = "DB 0"; } else if( gOp == "mode_DB1" ) { gTarget = "DB 1"; } else if( gOp == "mode_DB2" ) { gTarget = "DB 2"; } else if( gOp == "mode_DB3" ) { gTarget = "DB 3"; } else if( gOp == "mode_DB4" ) { gTarget = "DB 4"; } else if( gOp == "mode_DB5" ) { gTarget = "DB 5"; } else if( gOp == "mode_DB6" ) { gTarget = "DB 6"; } else if( gOp == "mode_DBA" ) { gTarget = "DB A"; } else if( gOp == "mode_DBC" ) { gTarget = "DB C"; } else if( gOp == "mode_DBF" ) { gTarget = "DB F"; } else if( gOp == "keyexists" ) { llDialog( gAvKey, "key exists Check Type", [ "key_val", "key_bad" ], gCommandChannel ); } else if( gOp == "key_val" ) { getRandomTest(); llSay(0,"DB_TEST Good keyexists check"); llMessageLinked( LINK_THIS, 6, gTarget+"|" + "db_keyexists|" + (string)gTestKey, gMe ); } else if( gOp == "key_bad" ) { makeTest( - 1 ); llSay(0,"DB_TEST Bad keyexists check"); llMessageLinked( LINK_THIS, 7, gTarget+"|" + "db_keyexists|" + (string)gTestKey, gMe ); } else if( gOp == "valexists" ) { llDialog( gAvKey, "key exists Check Type", [ "val_val", "val_bad" ], gCommandChannel ); } else if( gOp == "val_val" ) { getRandomTest(); llSay(0,"DB_TEST Good valexists check"); llMessageLinked( LINK_THIS, 8, gTarget+"|" + "db_valexists|" + (string)gTestValue, gMe ); } else if( gOp == "val_bad" ) { makeTest( - 1 ); llSay(0,"DB_TEST Bad keyexists check"); llMessageLinked( LINK_THIS, 9, gTarget+"|" + "db_valexists|" + (string)gTestValue, gMe ); } else if( gOp == "fetchs" ) { llDialog( gAvKey, "Fetch Type\n" + " fetch_val - valid key fetch\n" + " fetch_bad - invalid key fetch\n" + " fetchndx_val - valid index fetch\n" + " fetchndx_bad - invalid index fetch\n" + " fetchall - fetch all", [ "fetch_val", "fetch_bad", "fetchndx_val", "fetchndx_bad", "fetchall" ], gCommandChannel ); } else if( gOp == "fetch_val" ) { getRandomTest(); llSay(0,"DB_TEST Good fetch check"); llMessageLinked( LINK_THIS, 10, gTarget+"|" + "db_fetch|" + (string)gTestKey, gMe ); } else if( gOp == "fetch_bad" ) { makeTest( -1 ); llSay(0,"DB_TEST Bad fetch check"); llMessageLinked( LINK_THIS, 11, gTarget+"|" + "db_fetch|" + (string)gTestKey, gMe ); } else if( gOp == "fetchndx_val" ) { getRandomTest(); llSay(0,"DB_TEST Good fetch check"); llMessageLinked( LINK_THIS, 12, gTarget+"|" + "db_fetchndx|" + (string)gTestNbr, gMe ); } else if( gOp == "fetchndx_bad" ) { integer len = llGetListLength( gTests ); llSay(0,"DB_TEST Bad fetch check"); llMessageLinked( LINK_THIS, 13, gTarget+"|" + "db_fetchndx|" + (string)(len + 1), gMe ); } else if( gOp == "fetchall" ) { llMessageLinked( LINK_THIS, 14, gTarget+"|" + "db_fetchall", gMe ); } else if( gOp == "entries" ) { llMessageLinked( LINK_THIS, 15, gTarget+"|" + "db_entries", gMe ); } else if( gOp == "memory" ) { llMessageLinked( LINK_THIS, 16, gTarget+"|" + "db_memory", gMe ); } else if( gOp == "clear" ) { llMessageLinked( LINK_THIS, 17, gTarget+"|" + "db_clear", gMe ); gTestNbr = 0; gTests = []; gDeletedTests = [];
} else if( gOp == "sort" ) { // // build sort command in steps if( gTarget != "DBMeta" ) { gSortCmd = "DBSort|db_sort|"+gTarget+"|"; } else { gSortCmd = gTarget+"|db_sort|X|"; } llDialog( gAvKey, "\nSort type", [ "sort_key", "sort_val" ], gCommandChannel ); } else if( gOp == "sort_key" || gOp == "sort_val" ) { gSortCmd += llGetSubString( gOp, 5,7 )+"|"; llDialog( gAvKey, "\nSort Dir", [ "sort_ascend", "sort_descend" ], gCommandChannel ); } else if( gOp == "sort_ascend" || gOp == "sort_descend" ) { gSortCmd += llGetSubString( gOp, 5,-1)+"|"+(string)gSortBite; // // Iniiate the sort llMessageLinked( LINK_THIS, 18, gSortCmd, gMe ); } else if( gOp == "db_response" ) { llSay(0, "DB_TEST db_response ["+gOperand+"]"); } else if( gOp == "keystress" ) { string base = "00000000-0000-0000-0000-00000000"; string tmpKey; integer i; llMessageLinked( LINK_THIS, 17, gTarget+"|" + "db_clear", gMe ); llMessageLinked( LINK_THIS, 16, gTarget+"|" + "db_memory", gMe ); for( i = 0 ; i < 200 ; i++ ) { llSay(0, "DB_TEST Stress Add "+(string)i); tmpKey = base + (string)( 100 + i ); llMessageLinked( LINK_THIS, 2, gTarget+"|" + "db_add|" + tmpKey+"|" + "", gMe );
llSleep(0.05); } llMessageLinked( LINK_THIS, 16, gTarget+"|" + "db_memory", gMe ); llMessageLinked( LINK_THIS, 16, gTarget+"|" + "db_list", gMe ); llMessageLinked( LINK_THIS, 16, gTarget+"|" + "db_entries", gMe ); llMessageLinked( LINK_THIS, 16, gTarget+"|" + "db_memory", gMe ); } return;
}
//==========================================================// // // // States // // // //==========================================================//
// // default. Since the particle system is set it and forget it, the // default state's primary responsiblity is to handle communications. // Command input can come from chat, notecards and from other linked // prims. //
default {
state_entry() { // // on startups, try to read the default configuration notecard // (if it exists) if( gSetup ) { gSetup = FALSE; llSetRemoteScriptAccessPin( 0 ); gMe = llGetScriptName(); gSaltBase = (llGetUnixTime() % llGetListLength(gSaltChars)) + 1; } commReset();
} // // process commands from chat listen( integer channel, string name, key id, string message ) { gAvKey = id; gAvName = llKey2Name( gAvKey ); parseCommand( message ); processCommand(); } touch_start( integer nbr ) { gAvKey = llDetectedKey(0); gAvName = llKey2Name( gAvKey ); llDialog( gAvKey, "Monitor History Tester", [ "misc", "list", "mode", "add", "delete", "update", "keyexists", "valexists", "fetchs", "sort", "keystress" ], gCommandChannel ); } // // or the control can come from inside the linkset link_message(integer senderNbr, integer nbr, string message, key id) { gAvKey = id; gAvName = llKey2Name( gAvKey ); gMsgId = nbr; gSenderNbr = senderNbr;
if( parseLinkCommand( message ) ) { processCommand(); } } on_rez( integer param ) { llResetScript(); }
}
</lsl>