mem_array.lslp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. //
  2. // The line above should be left blank to avoid script errors in OpenSim.
  3. /**********************************************************************************************
  4. * mem_array.lsl
  5. * Copyright (c) 2009 Paul Preibisch
  6. * Released under the GNU GPL 3.0
  7. * This script can be used in your scripts, but you must include this copyright header as per the GPL Licence
  8. * For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
  9. *
  10. *
  11. * This script is part of the SLOODLE Project see http://sloodle.org
  12. *
  13. * Contributors:
  14. * Paul G. Preibisch (Fire Centaur in SL)
  15. * fire@b3dMultiTech.com
  16. /**********************************************************************************************
  17. /**********************************************************************************************
  18. * INTENT
  19. * This script was created in order to add a gaming element to the SLOODLE Educational Project
  20. * See http://sloodle.org
  21. * I've used an earlier version of it, in my zombie attacks game here:
  22. * http://www.youtube.com/watch?v=LPowKJA6Tnw&feature=channel_page
  23. *
  24. * By using this script, developers can store data in SL using linked messages without having to worry about
  25. * storage limitations of LSL Lists (providing there are enough mem_array scripts in the prim)
  26. *
  27. * Using this script, I am able to download student data from a mysql database directly into lsl memory
  28. * This way, my scripts can respond faster to in-world events without having to perform HTTP requests during
  29. * times which require faster responses. Make sure you compile this script in mono to increase memory availability
  30. *
  31. * I hope this script is useful to you, and encourages you to make some exciting new scripts and help us all
  32. * expand the virtual world - and maybe even make a few educational games!
  33. * I'd also like to thank Linden Lab for developing a platform where 3d content can be created and shared in a
  34. * Massively Multiplayer environment. I hope others will emerge soon and join the fun!
  35. *
  36. * Sincerely, Paul Preibisch
  37. *
  38. * PURPOSE
  39. * The Purpose of this script is to increase the amount of data we can store using lsl lists. Instead of
  40. * storing data in a local list in your script, you can instead issue a llMessageLinked message to store the data
  41. * in the mem_array scripts. These scripts work together in a chain like pattern. Ie: commands are always
  42. * issued to
  43. * the first mem_array script, then, go to mem_array 1, mem_array 2 etc, until the last mem_array script is reached.
  44. * In this way, the calling script can operate independantly of the mem_array scriots, and doesnt need to keep track
  45. * of how many mem_array scripts there are, it simply needs to issue
  46. * commands, and wait for a response.
  47. *
  48. * I've modelled this script after a typical database system which has rows and columns.
  49. * Therefore, you'll see that I've used lsl lists to represent "Columns" in a database table. And
  50. * individual list items consititute a table row. So, for example
  51. * the entire database table in this script is comprised of:
  52. * list column0;
  53. * list column1;
  54. *
  55. * To print Row 0 of the table would then be: llSay(0,llList2String(column0,0) +", " + llList2String(column1,0);
  56. *
  57. * You'll also notice that I've made use of several constant values throughout the script
  58. * I've designed this script in this way, in order to be easy to read as a developer.
  59. * So, when sending a linked message, I don't just send the data, alone like: INSERT|somedata|somedata
  60. * But instead, I preface the data with contextual dextriptions
  61. * ie: COMMAND:INSERT|COLUMN0:some data|COLUMN1:somedata
  62. * In this way, the developer can easily see what is being sent in the linked message. This may seam unnecessary
  63. * but since LindenLab, or another 3rd party hasn't developed a code stepper where
  64. * we can watch the values of our variables etc
  65. * during the debug stage, this can greatly help to see what is going on using a few llOwnerSay
  66. * commands in the linked_message event.
  67. * --------------------------------------------------------------------------------------------------
  68. * INSERTING DATA
  69. * --------------------------------------------------------------------------------------------------
  70. * To insert data into the chain, I've created a memory_controller script. As mentioned, this script operates independantly
  71. * of the mem_array scripts. It simply needs to issue an insert command, and the mem_array scripts will handle the rest.
  72. *
  73. * llMessageLinked(LINK_SET, 0, "COMMAND:INSTERT|COLUMN0:some data|COLUMN1:some data", NULL_KEY);
  74. *
  75. * WHAT HAPPENS:
  76. * Since link_num was set to 0 when this command was executed, only the first mem_array script will respond.
  77. * It will first check if it has any free memory, if it does, it will store the column0 data in a list called column0, and the column1 data in a list called column1
  78. * If you need to save more than 2 data elements, simply modify this script by adding more lists - called column2,column3 etc
  79. *
  80. * If the mem_array script is getting full, instead of inserting the data in it's column lists, it will instead relay the INSERT message to the next
  81. * mem_array script. (ie: if "mem_array 1" is full, it will pass the insert message to "mem_array 2". If all mem_arrays are full, then the last
  82. * mem_array script will report an error to the calling script, via a linked message. Thus, all commands trickle down through the chain until
  83. * the end of the chain.
  84. * --------------------------------------------------------------------------------------------------
  85. * RETRIEVING DATA
  86. * --------------------------------------------------------------------------------------------------
  87. * To retrieve data, you simply need to issue the following linked message:
  88. *
  89. * llMessageLinked(LINK_SET,0,"COMMAND:GETDATA|COLUMN0:some data, NULL_KEY);
  90. *
  91. * Example: Maybe you are using these to store a list of visitor names. Then you issue:
  92. * llMessageLinked(LINK_SET,0,"COMMAND:GETDATA|COLUMN0:Fire Centaur, NULL_KEY);
  93. *
  94. * WHAT HAPPENS:
  95. * "mem_array" (the first script) would receive the command, search through column0, and if found, send a linked message
  96. * back to your script with the other columns appended.
  97. * a typical response would be:
  98. *
  99. * RESPONSE:FOUND SEARCHITEM0|COLUMN0:Fire Centaur|COLUMN1:other data which was stored
  100. *
  101. *
  102. * --------------------------------------------------------------------------------------------------
  103. * REMOVING A ROW
  104. * --------------------------------------------------------------------------------------------------
  105. * To remove a row, simply issue the following:
  106. *
  107. * llMessageLinked(LINK_SET,0,"COMMAND:REMOVE|COLUMN0:Fire Centaur", NULL_KEY);
  108. *
  109. * WHAT HAPPENS:
  110. * "mem_array" (the first script) would receive the command, search through column0, and if found,
  111. * remove the data from column0, column1 etc
  112. * If the data was found, then the mem_array will send back a sucess response to the memory controller (listed below).
  113. * If mem_array can't find the data to be removed, it will relay the remove command to the next mem_array script.
  114. * If the command trickles down to the last link in the chain without finding the data to be removed
  115. * an error response will be sent back to the memory controller script
  116. *
  117. * RESPONSE:REMOVED ROW|COLUMN0:Fire Centaur|COLUMN1:other data
  118. *
  119. * or:
  120. *
  121. * RESPONSE:NOT FOUND, REMOVE CANCELED|COLUMN0:Fire Centaur
  122. *
  123. * --------------------------------------------------------------------------------------------------
  124. * LISTING rows
  125. * --------------------------------------------------------------------------------------------------
  126. * For testing purposes, I added a LIST command that will simply print out all rows stored.
  127. *
  128. * llMessageLinked(LINK_SET,0,"COMMAND:LIST", NULL_KEY);
  129. *
  130. * WHAT HAPPENS:
  131. * "mem_array" (the first script) would receive the command, and simply iterate through the lists llSaying each data field
  132. * When complete, the LIST command is sent to the next mem_array
  133. *
  134. * --------------------------------------------------------------------------------------------------
  135. * COUNTING DATA ELEMENTS
  136. * --------------------------------------------------------------------------------------------------
  137. * To remove a retrieve a count of all the data elements stored in the mem_arrays, simply issue the following:
  138. *
  139. * llMessageLinked(LINK_SET,0,"COMMAND:COUNT|COLUMN0:0", NULL_KEY);
  140. *
  141. * WHAT HAPPENS:
  142. * "mem_array" (the first script) would receive the command, count the number of elements in column0
  143. * and then relay this total to the next mem_array
  144. * The next mem_array would add the amount sent to it's count, and relay the message to the next.
  145. * This continues until the last link in the chain is reached, and then the grand total is sent back to the memory controller
  146. *
  147. * RESPONSE:COUNT|number of rows stored
  148. *
  149. **********************************************************************************************/
  150. integer MIN_MEMORY_LIMIT=2000;
  151. integer COLUMN0=1; //a constant used in llList2String commands to make code more readable
  152. integer COLUMN1=2; //a constant used in llList2String commands to make code more readable
  153. integer COMMAND=0; //a constant used in llList2String commands to make code more readable
  154. integer DATA0;//a constant used in llList2String commands to make code more readable
  155. integer DATA1;//a constant used in llList2String commands to make code more readable
  156. integer SEARCHITEM0=1; //a constant used in llList2String commands to make code more readable
  157. string searchItem0; //local var used when searching through columns
  158. integer count; //used to get the cummulative total of all elements stored in the mem_arrays
  159. integer prevTotal; //used to get the cummulative total of all elements stored in the mem_arrays
  160. integer true=1; //a constant used to make the code more readable
  161. integer false=-1; //a constant used to make the code more readable
  162. integer MEMORY_CONTROLLER=-1; //MEMORY CONTROLLER USES 0 FOR link_num for linked messages
  163. integer myLinkNum; //This is used to identify the script
  164. list column0; //this can be seen as column0 of a table row, to add more data elements, add column2,3,4 throughout the code
  165. list column1; //this can be seen as column1 of a table row, to add more data elements, add column2,3,4 throughout the code
  166. string data; //temporary vars
  167. string data0; //temporary var, represents a single data field value in column0
  168. string data1; //temporary vars, represents a single data field value in column1
  169. integer LAST_LINK_IN_MEM_ARRAY=-1; //switch var, which indicates if this is the last link in the chain. Determined by script name
  170. list mem_array_script_list; //used to count how many mem_array's exist in the prim
  171. integer s; //local var used in count_mem_array_scripts function to determin number of scripts in the prim
  172. integer numArrays; //local var used in count_mem_array_scripts function to determin number of scripts in the prim
  173. list message; //local var used in linked_message events
  174. integer foundIndex;//local var used to search lists
  175. integer i; //local var used for for loops
  176. /***********************************************
  177. * clean()
  178. * Is used so that sending of linked messages is more readable by humans. Ie: instead of sending a linked message as
  179. * GETDATA|50091bcd-d86d-3749-c8a2-055842b33484
  180. * Context is added instead: COMMAND:GETDATA|PLAYERUUID:50091bcd-d86d-3749-c8a2-055842b33484
  181. * By adding a context to the messages, the programmer can understand whats going on when debugging
  182. * All this function does is strip off the text before the ":" char
  183. ***********************************************/
  184. string clean(string cmd){
  185. return llList2String(llParseString2List(cmd, [":"],[]),1);
  186. }
  187. /***********************************************
  188. * count_mem_array_scripts()
  189. *
  190. * This function will count the number of mem_array scripts that exist,
  191. * and will check if this is the last mem_array script in the chain
  192. *
  193. ***********************************************/
  194. integer count_mem_array_scripts()
  195. {
  196. mem_array_script_list = [];
  197. s = llGetInventoryNumber(INVENTORY_SCRIPT);
  198. numArrays=0;
  199. while(s){
  200. string scriptName = llGetSubString(llGetInventoryName(INVENTORY_SCRIPT, --s),0,8);
  201. if (scriptName=="mem_array")
  202. numArrays++;
  203. }
  204. if (myLinkNum == (numArrays-1))
  205. LAST_LINK_IN_MEM_ARRAY = 1;
  206. else LAST_LINK_IN_MEM_ARRAY = -1;
  207. return LAST_LINK_IN_MEM_ARRAY;
  208. }
  209. default
  210. {
  211. state_entry()
  212. {
  213. llSetTextureAnim( ANIM_ON|LOOP|SMOOTH, ALL_SIDES, 1, 1, 0, 1, -0.05 );
  214. myLinkNum = (integer)llGetSubString(llGetScriptName(), 9, -1);
  215. llSay(0,(string)llGetFreeMemory());
  216. count_mem_array_scripts();
  217. }
  218. /***********************************************
  219. * link_message
  220. *
  221. * SOURCES:
  222. * Messages come from memory_controller, or other mem_array
  223. *
  224. * MESSAGES:
  225. * COMMAND:GETUUID|SEARCHITEM0:someuuid
  226. ***********************************************/
  227. link_message(integer sender_num, integer mem_array_link, string str, key id) {
  228. list message=llParseString2List(str, ["|"],[]);
  229. /*
  230. * This script will only respond to this link_message event if the link_message is for us. This is determinted by looking at mem_array_link var, and comparing
  231. * that with our script name. If mem_array_link is the same as our script number, ie: if this is mem_array 1, then
  232. * our script number is 1
  233. */
  234. if (mem_array_link==myLinkNum){
  235. /*
  236. * "GETPLAYER" is a search message sent from the MEMORY CONTROLLER
  237. * The message sent from the memory controller looks like this:
  238. * COMMAND:GETDATA|SEARCHITEM0:some data
  239. */
  240. if (clean(llList2String(message,COMMAND)) == "GETDATA")
  241. {
  242. searchItem0=clean(llList2String(message,SEARCHITEM0)); //retrieve searchItem0 from the linked message
  243. foundIndex= llListFindList(column0,[searchItem0]); //search through column0 for searchItem0
  244. //FOUND: If the SEARCHITEM0 was found, then we can send back data
  245. if (foundIndex != -1)
  246. {
  247. //This mem_array contains several lists. Each list pertains to a particular field of data
  248. //in our case we are only storing two fields of data per row.
  249. //[COLUMN0][COLUMN1]. Therefore, index 0 of column0, and scoreboardUuidList represent one row of data
  250. //In order to hold more field values, simply add another list to this script. Example:
  251. //We could add another list called: playerPoints, then, a typical row of data would be:
  252. //[COLUMN0][COLUMN1][COLUMN2]
  253. //We will now return COLUMN0, and COLUMN1 to the Memory_controller
  254. llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "COMMAND:FOUND SEARCHITEM0|COLUMN0:"+llList2String(column0,foundIndex)+"|COLUMN1:"+llList2String(column1,foundIndex), NULL_KEY);
  255. }else { //NOT FOUND
  256. /*
  257. * The SEARCHITEM0 is NOT held in this mem_array. Therefore, we must tell the next mem_array in the chain to
  258. * search for the SEARCHITEM0. If this is the last mem_array in the chain, then we must send a NOT FOUND message back
  259. * to the MEMORY_CONTROLLER
  260. */
  261. if (LAST_LINK_IN_MEM_ARRAY==true)
  262. llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:NOTFOUND|COLUMN0:"+searchItem0, NULL_KEY);
  263. else
  264. //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search
  265. llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:GETDATA|COLUMN0:"+searchItem0, NULL_KEY);
  266. }
  267. }
  268. /*
  269. * "INSERT" is a request sent from the MEMORY CONTROLLER to store data
  270. *
  271. * The message sent from the memory controller looks like this:
  272. * COMMAND:INSERT|DATA0:some data|DATA1:some data
  273. */
  274. else if (clean(llList2String(message,COMMAND))=="INSERT"){
  275. data0=clean(llList2String(message,1));
  276. data1=clean(llList2String(message,2));
  277. data="COLUMN0:"+data0+"|COLUMN1:"+data1;
  278. if (llGetFreeMemory()> MIN_MEMORY_LIMIT){
  279. column0+=data0;
  280. column1+=data1;
  281. llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:INSERT COMPLETE|"+data, NULL_KEY);
  282. llSay(0,llGetScriptName()+" inserted data");
  283. }else{
  284. //the memory is full, so we need to pass insert command to the next mem_array, but if we are the last mem_array, we need to report an error
  285. if (LAST_LINK_IN_MEM_ARRAY==true){
  286. llRemoteLoadScriptPin(llGetKey(), "mem_array",5577, TRUE, 0);
  287. //llOwnerSay(llGetScriptName()+":LAST mem_array ********* mem error "+str);
  288. llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:MEMORY ERROR|"+data, NULL_KEY);
  289. }else{
  290. //llOwnerSay(llGetScriptName()+":********* REALYING INSERT "+str);
  291. //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search and destroy
  292. //MESSAGE FORMAT: COMMAND:INSERT|DATA0:some data
  293. llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:INSERT|"+data, NULL_KEY);
  294. }
  295. }
  296. }
  297. /*
  298. * "REMOVE" is a request sent from the MEMORY CONTROLLER to REMOVE a row data
  299. *
  300. * The message sent from the memory controller looks like this:
  301. * COMMAND:REMOVE|DATA0:some data
  302. */
  303. else if (clean(llList2String(message,COMMAND))=="REMOVE"){
  304. llOwnerSay(llGetScriptName()+":in REMOVE "+str);
  305. searchItem0=clean(llList2String(message,SEARCHITEM0)); //retrieve searchItem0 from the linked message
  306. foundIndex= llListFindList(column0,[searchItem0]); //search through column0 for searchItem0
  307. //FOUND: If the SEARCHITEM0 was found, then delete it from this mem_array
  308. if (foundIndex != -1)
  309. {
  310. llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:REMOVED ROW|COLUMN0:"+llList2String(column0,foundIndex)+"|COLUMN1:"+llList2String(column1,foundIndex), NULL_KEY);
  311. column0 = llDeleteSubList(column0, foundIndex, foundIndex);
  312. column1 = llDeleteSubList(column1, foundIndex, foundIndex);
  313. }else { //NOT FOUND
  314. /*
  315. * The SEARCHITEM0 is NOT held in this mem_array. Therefore, we must tell the next mem_array in the chain to
  316. * search for the SEARCHITEM0 and if found delete it.
  317. * If this is the last mem_array in the chain, then we must send a NOT FOUND message back
  318. * to the MEMORY_CONTROLLER
  319. */
  320. if (LAST_LINK_IN_MEM_ARRAY==true){
  321. llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:NOT FOUND, REMOVE CANCELED|COLUMN0:"+searchItem0, NULL_KEY);
  322. }else{
  323. //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search and destroy
  324. //MESSAGE FORMAT: COMMAND:REMOVE|DATA0:some data
  325. llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:REMOVE|COLUMN0:"+searchItem0, NULL_KEY);
  326. }
  327. }
  328. }
  329. /*
  330. * "LIST" is a request sent from the MEMORY CONTROLLER to LIST all data row data
  331. *
  332. * The message sent from the memory controller looks like this:
  333. * COMMAND:LIST
  334. */
  335. else if (clean(llList2String(message,COMMAND))=="LIST"){
  336. for (i=0;i<llGetListLength(column0);i++)
  337. llOwnerSay(llGetScriptName()+" "+(string)i+": "+llList2String(column0, i)+", "+llList2String(column1, i));
  338. llMessageLinked(ALL_SIDES, myLinkNum+1, "COMMAND:LIST", NULL_KEY);
  339. }
  340. /*
  341. * "COUNT" is a message sent from the MEMORY CONTROLLER. It will go through each mem array and count
  342. * how many values are currently stored
  343. * The message sent from the memory controller looks like this:
  344. * COMMAND:COUNT
  345. * After counting, it will relay the count to the next mem_array, until the last mem_array is visited.
  346. * The cummulative total will be sent back to the memory controller
  347. */
  348. if (clean(llList2String(message,COMMAND)) == "COUNT"){
  349. count = llGetListLength(column0);
  350. prevTotal = (integer)clean(llList2String(message,COLUMN0));
  351. count+= prevTotal;
  352. if (LAST_LINK_IN_MEM_ARRAY==true){
  353. llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:COUNT|COLUMN0:"+(string)count, NULL_KEY);
  354. }else{
  355. //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search and destroy
  356. //MESSAGE FORMAT: COMMAND:REMOVE|DATA0:some data
  357. llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:COUNT|COLUMN0:"+(string)count, NULL_KEY);
  358. }
  359. }
  360. }
  361. }
  362. /***********************************************
  363. * changed event
  364. *
  365. * Every time the inventory changes, we must count the number of mem_array scripts to determin if this is the last
  366. * mem_array in the chain. This is important because, if we ARE the last mem_array script, we are responsible for
  367. * sending NOT FOUND messages back to the memory controller if searches through the entire chain of mem_array scripts fail.
  368. *
  369. ***********************************************/
  370. changed(integer change) {
  371. if (change ==CHANGED_INVENTORY){
  372. count_mem_array_scripts();
  373. }
  374. }
  375. }
  376. // Please leave the following line intact to show where the script lives in Git:
  377. // SLOODLE LSL Script Git Location: assets/misc/mem_array.lslp