Instead of arrays, LSL uses lists. The list type is exactly what it sounds like: a heterogeneous list of the other data types. Lists are created via comma-separated values (CSV) of the other data types. enclosed by square brackets: “[” and “]”. See below for examples of the syntax.
Lists not only store the value of the list item, but also its type (see llGetListEntryType and llCSV2List). To directly enter a float value into a list, a decimal point (.) must be used.
Because they store multiple value types, lists are not accessed with square brackets like arrays are. They are read by accessor functions that specify the type of value attempting to be retrieved (e.g. llList2Integer) and written with accessor functions that insert or replace values (or ranges of values) based on new list variables (e.g. llListReplaceList).
Note: while the LSL compiler will only accept code with a maximum of 72 items in a list, lists can actually support as many items as a script’s memory will allow. If a list of more than 72 predefined items is needed, just concatenate (combine) two predefined lists into a new one: longlist = firstpart + secondpart;
As of 1.20.4, this no longer seems to be true. A list with at least 100 is possible, though I haven’t tested to see just how many elements I can initialize a list to. – BlueJeansAndRain
June 5, 2008 – Server 1.22.1.88473 | Client 1.19.1 (4): I was able to create a global list containing 1,081 null integers and a local list containing 2,318 null integers before receiving “(0, 0) : Byte code assembly failed — out of memory”. – CoryFimicoloud
Lists cannot contain other lists, so using them like a multidimensional array is not possible natively (although several ways around this are available, notably this script). Also, strided lists can be used.
It’s also worth noting that with functions, lists are not referenced; they are duplicated locally. This means that, as an example:
iUseLists(list input){...}
handed the list:
list oh_hi = [data here];
results in “input” being a duplicate of the data held in “oh_hi”, rather than oh_hi being operated on. This is important, as many higher level languages (the most obvious in my mind being java) operate on the original list (contextually, array). In practice, this means that a scripter attempting to use this functionality erroneously could easily end up flooding their script memory with a large list, and not having their expected results in the original list as well.
Example:
//a list with a string, integer, float, vector and rotation
list l = ["somestring",12,3.0,<3.3,4,0>,<0,0,0,1>];
Lists can be concatenated by using +.
Example:
newlist = oldlist + ["oneitem","anotheritem"];
Thus, to add an element to the (end) of an existing list:
Example:
myList = myList + [new_item];
or
myList += [new_item];
This voodoo magic will allow appending new elements in a memory efficient fashion (thanks to BlindWanderer):
myList = (myList=[]) + myList + ["new_item"];
Don’t ask me why but I can save 3-4KB of memory with adding 90 elements. – PsykePhaeton
Is this broken as of Aug-21-2007? It doesn’t appear to work – VagnerSousaBR
“This voodoo magic seems to work fine. Just pay attention to the “new_item” square brackets when it’s already returned as a list type in a function like llParseString2List(). I got a stack-heap collision error when I was composing an huge list by the usual way. This way has worked fine.”
This works because LSL makes a temporary copy of all variables in an expression before it evaluates it. Say you’re concatenating two 2kb lists: You originally use a total of 4kb of memory. The normal method of concatenation would copy them (then requiring a total of 8kb), then concatenate them (total of 12kb), then copy them into the original variable (14kb), then destroy all the temporary copies. The “voodoo magic” here is that after the temporary copies are made for evaluating the expression, the original variable is cleared, requiring extra memory only during the copy. This gives the rest of the operation a significant amount of breathing room. -DunselHodgson
This trick is no longer needed with Mono. See the Q&A section for evidence. – MimikaOh
It seems that this trick can also be used to great effect when passing lists to functions that change the list and then overwrite the same list with the new copy, such as llDeleteSubList. From my testing on a large sample list using calls to llGetFreeMemory(), this appears to use no memory at all, where the normal way of doing it would use over 2000 bytes. I’m not sure if these figures are really correct, but it certainly made it a lot less likely to cause a stack/heap collision when the list was very big, so it must be saving at least some memory, and the resulting list was as expected. The syntax for this is as follows:
myList = llDeleteSubList((myList=[])+myList, 0, 0);
Instead of the normal way, which would be:
myList = llDeleteSubList(myList, 0, 0);
Note: This temporarily clears the original list variable before saving the result back into it, so if you save the result in a different variable instead, the original one will end up empty after this. –EddyOfarrel
EddyOfarrel results appear correct that the ‘voodoo’ trick works in the list modification functions. My memory usage results can be found on my user page. – CoryFimicoloud
Q: Does anyone know whether any of this applies to scripts compiled with Mono? (From my initial testing, it seems like using this kind of trick makes no difference to the amount of memory used by the operation in a Mono script, and may actually cause the script to use more memory to start with. I suspect this probably makes sense, as Mono probably doesn’t do things in such an inefficient way to start with, but it would be nice if someone who knows more about how exactly this trick and Mono work could confirm this.) -EddyOfarrel
I believe it does not apply to Mono. Here is my test.
default { state_entry() { list l; integer i = 0; while (TRUE) { l = l + [i]; // or add the (l=[]), see below if (i % 100 == 0) llOwnerSay((string)i); ++i; } } }
Before Mono this reaches 1000 with the voodoo trick, 400 without. With Mono it reaches 3100 with or without the voodoo. — MimikaOh
To clear a list, set it equal to nothing:
Example:
myList = [];
To search a list for a value, use llListFindList
list myList = ["A","B","C","D","E","F"]; string searchFor = "C"; integer index = llListFindList( myList, [searchFor] ); if ( index != -1 ) llOwnerSay( searchFor + " was found in myList at position " + (string)index ); else llOwnerSay( searchFor + " was not found in myList" );
Refer to a list’s elements by their index number. Lists have 0-based indexing, where the first element has index 0.
Example:
list myList = ["A","B","C"]; string element; element = llList2String(myList,0); // element now has the value "A". element = llList2String(myList,1); // element now has the value "B". element = llList2String(myList,2); // element now has the value "C".
The value llGetListLength() returns the number of elements in the list. Therefore, the last element has index = (llGetListLength(myList) - 1).
Example:
list myList = ["A","B","C"]; integer listLength = llGetListLength(myList); // listLength equals 3. string element = llList2String(myList,listLength); // myList doesnt have an element at index 3 - this will return an empty string. string element = llList2String(myList,listLength - 1); // element now has the value "C".
Negative numbers can also be used to count backwards in a list. Therefore, the last element has index -1. The first element would also have an index of (-llGetListLength(myList))
Example:
list myList = ["A","B","C"]; integer listLength = llGetListLength(myList); // listLength equals 3. string element = llList2String(myList,-listLength); // element now has the value "A". string element = llList2String(myList,-1); // element now has the value "C".
Function Name | Purpose |
llGetListLength | Gets the number of elements in a list |
llCSV2List | Converts comma-separated values (CSV) to a list |
llList2CSV | Converts a list to comma-separated values (CSV) |
llDeleteSubList | Removes a portion of a list |
llList2List | Returns a portion of a list |
llListFindList | Returns the position (index) of the first instance of a sublist in a list |
llListInsertList | Inserts a list into a list |
llListReplaceList | Replaces a part of a list with another list |
llDumpList2String | Dumps a list to a string with a specified separating string |
llParseString2List | Parses a string to a list, using two lists of separators |
llParseStringKeepNulls | Similar to llParseString2List, but adds null values between spaces |
llList2ListStrided | Extracts a subset of a strided list |
llListSort | Sorts a list ascending or descending, as appropriate to type. |
llListStatistics | Performs statistical operations on a list composed of integers and floats. |
llListRandomize | Randomizes a list |
llGetListEntryType | Gets the type of entry of an element in list |
llList2Float | Returns the float element |
llList2Integer | Returns the integer element |
llList2Key | Returns the key element |
llList2Rot | Returns the rotation element |
llList2String | Returns the string element |
llList2Vector | Returns the vector element |
To move a list entry from src to dest:
list ssMoveListEntry(list myList, integer src, integer dest) { string entry = llList2String(myList, src); // Warning: will lose type information and may cause precision loss for float values myList = (myList=[]) + llDeleteSubList(myList, src, src); myList = (myList=[]) + llListInsertList(myList, (list)entry, dest + (dest > src)); return myList; }
If losing type information or precision is an issue use:
list ssMoveListEntry(list myList, integer src, integer dest) { list entry = llList2List(myList, src, src); myList = (myList=[]) + llDeleteSubList(myList, src, src); myList = (myList=[]) + llListInsertList(myList, entry, dest + (dest > src)); return myList; }
Strided Lists
Since lists cannot contain items of the list type, the only way to get something similar to a multidimensional array is using strides. A strided list is a regular list with items grouped together in a fixed layout.
Say you wanted to make a visitor tracker and keep track of visitor’s names, time spent on your land, and the date they visited last. You could use a strided list, where each stride consists of three items:
integer STRIDELENGTH = 3; // this is very helpful when dealing with strided lists to keep your code flexible list visitors = ["Ama Omega", 5, "2004-06-12", "Catherine Omega", 12, "2004-06-28", "Ezhar Fairlight", 25, "2004-06-30"]; //
Whenever you manipulate this list, you do it in complete strides, so to add another visitor to the list:
visitors += ["Mistress Midnight", 1, "2004-06-30"];
To sort the list by names in ascending order:
visitors = llListSort(visitors, STRIDELENGTH, TRUE);
To remove one or more strides from the list:
list DeleteSubListStrided(list src, integer start_stride_number, integer end_stride_number, integer stride_len) { return llDeleteSubList(src, start_stride_number * stride_len, (end_stride_number + 1) * stride_len - 1); } visitors = DeleteSubListStrided(visitors, 0, 0, STRIDELENGTH); // delete the first stride in the visitors list
To replace a stride in the list:
list UpdateSubListStrided(list src, list new_stride, integer stride_number, integer stride_len) { return llListReplaceList(src, new_stride, stride_number * stride_len, (stride_number + 1) * stride_len - 1); } visitors = UpdateSubListSTrided(visitors, ["Cynthia Russell", 1, "2008-10-02"], 2, STRIDELENGTH); // replace third entry with Cynthia's visit
The following functions have direct support for strided lists:
- llList2ListStrided
- llListRandomize
- llListSort
More examples of functions dealing with strided lists can be found on this page. LibraryStridedLists
Q & A
A: No, it takes much longer with growing list size. Better get used to LSL being painfully slow when manipulating large data sets (strings by far being the worst).
Re-Question: So, does that mean llListInsertList and llDeleteSubList actually make copies, i.e., reallocate, the entire list they’re operating on?
Re-Answer: Yup. LSL is a pass-by-value language. Any information passed to a function is copied to that function’s stack-frame before the function is run. This means that in practical terms, you need twice as much memory to insert or delete a sublist. (See the notes on the llListInsertList and llDeleteSubList pages to eliminate some confusion about the “manipulation” they do.)
A: LSL2 doesn’t support run-time typing, meaning you have to actually specify the type of each variable. The Lindens could have implemented a single function, llList2String, then required explicit typecasting, as in (integer)llList2String(foo, 0). However, that isn’t as memory- or CPU-efficient as llList2Integer(foo,0) (yes, really) and the amount of work required for the Lindens to implement seperate functions for each type was negligible.
You can still typecast values stored in a list, of course, but it’s advised to use the llList2* functions, unless it’s a vector or rotation stored as a string, in which case you should cast to a vector or rotation after using llList2String. llList2Vector and llList2Rotation do not cast from strings automatically. See the next question for more.
A: No. If a list element is a string containing text that would otherwise be directly typecast to a vector or rotation, It will not be converted. The resulting vector or rotation will be ZERO_VECTOR or ZERO_ROTATION respectively. This is a bug. To get around it, use llList2String to retrieve elements from your list, then cast them to vectors or rotations as needed: (vector)llList2String(positions,2);
Lists can be conveniently passed as a single string using the CSV functions. For example, this is useful for sending complex data between objects via listen or within linked objects with llMessageLinked. Or not, if you would be passing data with vectors, rotations, or strings with commas. llDumpList2String and llParseString2List are recommended for safe passing of lists. For an example of this method see ExampleListConversion.
I’ve tried running tests on memory usage, comparing mono and LSL. They both seem to use the same memory usage for storing lists. And trying to get some kind of formula about the memory usage of a list of a given length that’s populated purely with integers, I’m getting very schizophrenic results. A list consisting of just one integer uses 112 bytes of memory. 10 integers also used 112 bytes of memory. 100 integers used 1988 bytes. A list of 1000 integers used 20028 bytes. What does it seem that there’s an upwards curve in the memory usage as lists grow longer? And when the heck are we going to get access to proper arrays? – LuccaKitty
LuccaKitty, typically list allocates memory for certain number of elements (usually 10) immediately to avoid memory fragmentation and the cost of resizing, and that’s why you don’t see increase in memory usage until the 11th element is inserted in the list. – DomchiUnderwood