User:SuzannaLinn Resident/ScriptingClasses/Basics 3 - Lists
Basics 3 - Lists
Monday class
Lists
-> Object 1 List of Touchers and Chatters (a green sphere) -> Script 1 list of touchers and chatters
"list" is a type of variable like "string", "integer" or "float".
But instead of one value, it can take, as its name say, a list of values, for instance:
- list fruits = [ "Apple", "Banana", "Pear", "Melon", "Apricot" ];
- list primeNumbers = [ 2, 3, 5, 7, 11, 13, 17, 19 ];
The values have to be enclosed in square brackets and can be of any type, also floats, vectors, keys, etc. A list can have a mix of values of different types, but we are not going to mix types yet.
Please rezz the object "Object 1 List of Touchers and Chatters" from your class materials folder. It's a green sphere.
Now, using lists, we can get all the previous touchers of our object.
No need to add a variable for each toucher that we want to store.
With only one variable of type list we can store all the touchers.
The limit is the memory available in the script.
( script list of touchers and chatters )
( lines 0 - 4 )
We are going to store in the list touchId the id's of the different touchers.
We are also counting how many times each person has touched the object, using the list touchCount for the quantity of touches of each person.
These two lists are parallel lists. This means that we add elements to both lists at the same time.
When we add an id to touchId , we add a quantity to touchCount. Both lists have the same quantity of elements.
And the positions in the lists are related, for instance: if Suzanna is in the third position in the list touchId , the quantity of touches by Suzanna is in the third position in the list touchCount.
In the same way, we are going to listen to the public chat to store the chatters and how many times they have said something, with the lists listenId and listenCount.
The lists are:
- touchId : values of type key, the uuid's of the people that touch the object.
- touchCount : values of type integer, starting with 1, the quantity of touches by each person.
And:
- listenId : the uuid's of the people that say something in public chat.
- listenCount : the quantity of messages by each person.
( lines 85 - 118 )
Let's go to see the events.
In state_entry, line 89, we start listening to the public channel.
In the event listen we call addListen() in line 94. This is a user function. It is written above in the script and we will look at it later.
addListen() stores the name of the chatter and counts their messages.
We sent the uuid of the chatter to addListen( id ) as parameter.
The id is the only information that we need for our statistics, we are not interested in the message in this script.
Moving to touch_start, in lines 97-111.
Part of the code in the event is to look to all the touchers in total_number , using a loop for, as we saw it last week.
In lines 105-109 is the process for each toucher.
We do two different actions, depending on the toucher being the owner or anyone else.
If the toucher is the owner, line 105, we call the function sayInfo() , also a user function that we will see later.
sayInfo() says to the owner the information stored about chatters and touchers.
If the toucher is not the owner, we call addTouch() that stores the touchers and counts their touches.
As we have done in the event listen with addListen() we are sending the id of the toucher as parameter.
In the event on_rez, in line 115, we have commented out the reset. We will see why later.
We have three new user functions: addTouch(), addListen(), sayInfo(). Let's look at them.
( lines 6 - 18 )
We start with addTouch().
Here we want to add the toucher to the list, if the toucher is not there.
But if the toucher is there, we want to add 1 to their quantity of touches.
First we need to know if the toucher is in the list.
We use the LSL function llListFindList() in line 9.
This function has two parameters:
- the list where we want to look, in this case the list touchId.
- the element that we are looking for, the key personId.
Both parameters are of type list. The first parameter is clear that must be a list. The second seems more strange, because we are looking for a single element, not a list.
The problem is the strong typing of the variables in LSL.
LSL would need a different function to look for an integer, or for a string, or for a key,etc.
Their solution is to use a parameter of type list, because a list can contain any type of value.
So, we need to add [ and ] to make personId to be of type list: [ personId ]
llListFindList() returns the index of the element in the list, starting with 0. The first element has the index 0.
If the element is not in the list, llListFindList() returns -1.
We get the index in line 9, and we compare it to -1 in line 10, to know if we have a new toucher.
In case of a new toucher, lines 11-12, we add to both lists.
The operator +, when used with a list is not an arithmetical operator.
We are adding a new element to the list, at the end of it.
The code commented on the right of the lines, using [ and ], is also correct, and shows more clearly that we are adding a new element to the list.
But it's usually written without [ and ], like in the script, which save typing.
Remember, in line 12 we are not increasing something by 1, but adding an element 1 to the end of the list.
We are adding a new element to both lists at the same time. The lists grow together and always have the same number of elements, and the quantity of touches is in the same index than the toucher. We are making them parallel llists.
In case of a repeated toucher, we want to add one to their touches, in lines 14-16.
First, in line 14, we get the previous quantity of touches from the list touchCount.
llList2Integer() returns an element from the list.
The function has two parameters:
- the list where there is the element that we want, in this case the list touchCount.
- the index of the element, in this case index, which is the value return by llListFindList().
We need to use different functions depending on the type of the element that we want to get. There is also llList2String(), llList2Key() and others.
We increase the count in line 15.
In line 16 we replace the count in the list touchCount, with the function llListReplaceList().
llListReplaceList() has four parameters:
- the original list.
- the new value of the element to replace, as a list.
- the starting index of the replaced elements.
- the ending index of the replaced elements.
Usually we are only replacing one element, so the starting and ending index are the same, like in this script.
We could also replace several elements at the same time.
llListReplaceList() returns the replaced list, that we assign to the same list variable, in line 16.
If we don't use the returned value, it's not an error but does nothing:
- // WRONG! llListReplaceList(touchCount,[count],index,index); // WRONG!
( lines 20 -32 )
The next function is addListen().
It's exactly the same code than addTouch() with the list listenId and listenCount.
( lines 34-60 )
And the last new function is sayInfo().
We say the information to the owner.
llGetListLength() returns the quantity of elements in the list, its only parameter.
We get the total of touchers in line 40 and say it in line 41.
The loop for in lines 43-47 says all the touchers and their quantity of touches.
We are using the variable i as the index of the loop for. It's a tradition that comes from the old programming languages.
We could use, for instance, numberTouch as index. It's also good, but with i we save typing and make more clear which variable is in index of the for.
Lines 44 to 46 are all the same line, a llOwnerSay(). We split the line to make it more clear.
In line 44 we get the uuid of the toucher. It's a type key, so this time we use llList2Key(). And we use our familiar getFullName() for the name.
In line 46 we get the quantity of touches, typecasting it to string.
In line 49-60 we do the same with the chatters. In this case using a loop while, to see both loops.
It's not good to use this mix of for and while. In this kind of loop we can use for or while, as we prefer, but always the same one.
( lines 62 - 83 )
And getFullName() is like usual, no changes.
Answers to Questions
Is it better to use a list than using variables for each toucher?
- Yes, when we want to store several values of the same thing, a list is much better than using several variables
What exactly is list? Is it similar to an array?
- Yes, a list is similar to arrays in other languages, but only one dimension arrays because we cannot use lists of lists in LSL
How many elements have the lists? Is it defined at the beginning?
- No, the quantity of the elements is not defined.
- We can add more and more elements to a list, until the script runs out of memory.
- The only limit is the memory available in the script.
Is it possible to get all the four lists into one list?
- LSL doesn't allow lists of lists, so is much more limited than other languages.
- There is another way to use less lists, on a next class we will study this same example using only one list.
Can things easily go wrong with pararel lists?
- No, as long as our script does the equivalent action to each list in a parallel set, we won't have problems.
Can lists contain everything inside? Any type of data?
- Yes, lists can contain all types of data, except other lists.
Why is the default state so far along in the script?
- All the user functions, and also the global variables, have to be written before the "default" keyword.
- So the "default" with the events inside is always at the end of the script, more and more far along as we add more and more functions.
Why we need that info inside the parenthesis in addTouch( llDetectedKey(toucherCount) ) ?
- toucherCount is the index of the loop for.
- It goes from 0 to total_number-1 (the parameter of the event)
- We use toucherCount as the parameter of the function llDetectedKey() to get the toucher with this index.
- toucherCount is between parentheses because is the parameter of llDetectedKey().
- and llDetectedKey(toucherCount) is between parentheses because is the parameter of addTouch().
What is an index?
- An index is in general a counter
- We use an index for the loop for to count, in this case is toucherCount, counting from 0 to total_number-1.
- An index is also the number of the element in a list.
- And the parameter in llDetectedKey() is also a kind of index, because it goes from 0 to the quantity of touchers.
Why is toucherCont below the default state, but touchCount is above?
- touchCount is the list that will store the quantity of touches of each person
- We define it at the start of the script because is a global variable
- We want the lists to be there all the time, storing the values, so they need to be global.
- toucherCount is defined in line 100, before using it in the "for" in the event touch_start
- toucherCount is an integer that we use in the "for" as an index to get all the touchers with llDetectedKey().
- It's a local variable that only exists in touch_start.
How many times do we store the person if there are two touches?
- We store the person one time, and their quantity of touches.
- In the list touchId the id of the person is only one time.
- And in the list touchCount, in the same index, is the quantity, 2 in this case.
Does if ( index == -1 ) means "if the total count is equal or less than 1"?
- It means "if the number of index of the id of the person is equal to -1".
- -1 is a number of index that doesn't exist, because indexes start with 0 and go up.
- -1 is the value returned by llListFindList() when the element that we are looking for is not in the list.
- The first element in the list is index 0, the second element is index 1 and so on.
What are the brackets around the 1?
- The brackets around a value or a variable make it to be a list.
- We can add a list to another list, or a single element to a list.
- When we use the operator + with a list and another type of variable, LSL understands that the + means adding to a list, so we don't need to use the brackets around the element, they are optional.
- Since is more comfortable not to write the brackets, we usually don't write them.
Can we use both? either + 1 or + [1] ?
- Yes, we can use the code as it is in lines 11-12, or the code in the comments of these lines, the one that we prefer.
The content of "index" is different in addTouch and addListen, can we use the same names in two different functions?
- Index and count are defined in lines 7-8.
- They are local variables in the function addTouch, and they disappear when the function ends.
- So we can use the same names of variables in other functions.
- In this case, since they are doing the same in both functions, is good to use the same names.
When we add a list, do we add it all or only the element on the first index?
- When we add a list to another list, we add all the list.
Is indexing in a list the same than the parameter (integer total-number) in the touch_start event is for?
- llDetectedKey() works in some way like a list, but it's not really a list, so it's not the same.
- The count of touches with total_number in touch_start is one thing, and what we are doing in our function addTouch is completely independent of it
- total_number is the number of different touches served by this event call. Nearly always it will be 1, and most scripts ignore it and only handle the first toucher. However in a busy environment where it is vital to count each touch, you would loop through the touchers adding them each indivituallyl.
Why are we using "index, index" in the parameters of llListReplaceList?
- Because we only want to replace one element.
- For instance, if we replace element number 3, the first element to replace is number 3, and the last element to replace is number 3.
Is there a case when the start and end indexes differ?
- Yes, when replacing more than one element.
- Usually we only replace one element, so seeing "index, index" is common, but sometimes we replace several elements, and then it would be like "startIndex, endIndex"
- Note that llListReplaceList creates a brand new list out of the old list. We have to set the list variable to the new list to actually replace the old list with the new one with the replacement done. If we just call llListReplaceList without setting a variable to the return value, the change will be lost.
Where do we define and get personId?
- personId is the parameter of the function addTouch.
- We send it when we call addTouch, in the event touch_start line 108.
- We send to addTouch as parameter the id returned by llDetectedKey(toucherCount)
So we add the parameter personId to our user defined functions. And if someone touches the object, we fill it with the detected name of the toucher?
- Yes, when we call a function, we fill its parameters with the values that we want the function to use.
- We are using a parameter personId in three functions, and each funtion is called in different places, and the parameter is obtained in a different way.
- The important is that the types of the parameters of the function are the same as the types of the values or variables used to call them.
- In the function we can use the name that we like for the parameter.
- The function addTouch, for instance, wants to receive a value of type key.
- We need to call addTouch with a key, the function doesn't mind where we get it.
- addTouch uses the name personId for this key to refer to it inside the function, because we need a name to use it, but it doesn't mind how this key is named wherever the function is called
- The formal parameters of every function are completely unrelated to the parameters of any other function. For the sake of documentation sanity, we should name parameters with similar uses with similar names, but as far as the language is concerned, personId could be a key in one function, an unrelated integer in another, and a name string in a third. It would be poor practice to name things that way, but the program would compile and run and not complain at all.
Why its not good to mix for and while?
- For the script it doesnt mind, its for clarity, if we do the same thing is better to do it in the same way.
- Aesthetics mostly. Can make your code a tad harder to read.
Are "for" and "while" effectively the same thing?
- They are two ways to do the same thing.
- In some cases one fits better than the other, but they are rather interchangeable.
Is one of them preferable for some situations?
- When the condition is more complicated, fitting it into the line of the for can be difficult, and is better to use a while.
- And it cases like in this example, looping on the elements of a list, a for needs less lines, and with less lines we can see more of the script in the editor, which is good.
- But you can use the first that comes to your mind when you need one.
if the toucher touches the first time, we add the name to the list touchId. And we add the 1 (as a number element) to the list touchCount. When the toucher touches the second time, we search for the index of his id in the list touchID. And for the same index in list touchCount we increment the number plus 1?
- Yes, this is how it works.
Wednesday class
Lists review
Here is a quick review on what we have studied about lists:
To define a list we use the type "list". To initialize it we write its values between [and ]
- list fruits = [ "Apple", "Banana", "Pear", "Melon", "Apricot" ];
- list primeNumbers = [ 2, 3, 5, 7, 11, 13, 17, 19 ];
To add one element to the list we use the operator +
- fruits = fruits + "Orange";
We can add several element at a time, between [ and ]
- fruits = fruits + [ "Cherry", "Kiwi" ];
And also add elements at the start of the list
- fruits = "Peach" + fruits;
Now fruits is:
- [ "Peach", "Apple", "Banana", "Pear", "Melon", "Apricot", "Orange", "Cherry", "Kiwi" ]
To find an element in the list we use llListFindList()
- indexFruits = llListFindList( fruits, [ "Peach" ] ); // indexFruits will be 0
- indexPrimes = llListFindList( primeNumbers, [ 13 ] ); // indexPrimes will be 5
The element that we are looking for must be always among [and ], also if we use a variable
- string fruitName = "Mango";
- indexFruits = llListFindList( fruits, [ fruitName ] ); // indexFruits will be -1
When the element is not in the list, llListFindList() returns -1
To get an element from the list there is a function for each type of variable
- string fruitName = llList2String( fruits, 6 ); // fruitName wil be "Orange"
- integer primeValue = llList2Integer( primeNumbers, 1 ); // primeValue will be 3
There are also llList2Key(), llList2Vector() and others.
To get the total of elements in a list we use llGetListLength()
- totalFruits = llGetListLength( fruits ); // totalFruits will be 9
- totalPrimes = llGetListLength( primeNumbers ); // totalPrimes will be 8
To replace an element in the list we use llListReplaceList()
- fruits = llListReplaceList( fruits, [ "Plum" ], 5, 5); // element with index 5 is "Apricot"
- // [ "Peach", "Apple", "Banana", "Pear", "Melon", "Plum", "Orange", "Cherry", "Kiwi" ]
The new value must be between [ and ]
We can also replace several values
- fruits = llListReplaceList( fruits, [ "Strawberry" , "Blueberry", "Raspberry" ], 1, 3); // element with indexes 1 to 3 are "Apple", "Banana" and "Pear"
- // [ "Peach", "Strawberry" , "Blueberry", "Raspberry", "Melon", "Plum", "Orange", "Cherry", "Kiwi" ]
Practice: quick notes
Now, we're going to write a practice script—our first useful script in this series of classes.
We'll develop it step by step.
Step 1: The idea
The goal is to create a way to take quick notes that we can review later.
For example, it could be a piece of information, something mentioned in a chat, a link, or any thought that we want to remember but don't have time to organize immediately.
Step 2: The requirements
Next, let's design how our scripted object will work.
We're not focusing on the actual code yet, but rather on how users will interact with the object.
For example:
- Users will say to a a specific channel to record their notes.
- Users will touch the object to receive a list of their saved notes.
Step 3: The analysis
Now, let's think about what processes we need to include in the script to meet the requirements.
Again, we're not writing code just yet—just listing out what we need, such as global variables, functions, and events.
It might include:
- a list to store the notes
- the event listen with a channel for the notes
- the event touch_start to say the notes
- a function to add a note
- a function to say the notes
Step 4: Write the script
Let's get started!
Solution
-> Object 2 Quick Notes (a light blue box) -> Script 2 quick notes
In line 0, we define a variable with the number of the channel.
We could just use the number instead of a variable as we have been doing before.
But it's a good practice to use names instead of fixed values.
Because:
- It's easier to remember, when we are using the value in several places, especially when going back to work on the script after some time
- It's more clear when reading the script. We could be using several channels and is more difficult to know which number is which channel.
- It's easier to change the value, especially if it is used in several places. We only need to go to the start of the script and change it.
Perhaps it doesn't mind for this script, but it's good to start doing it. It will be useful when we write longer scripts.
We are writting the name of the channel all uppercase, with a _ to separate the words.
By convention, it means that this value is constant. We set it at the start, and it will not be changed.
Other languages has a way to define constant values, but not LSL. So we use the "all uppercase" convention.
In line 2 we define the list, quickNotes, to store the notes.
In lines 4-6, the function addNote(), adds a new note to the list.
We will call this function from the event listen, with the received message as parameter.
In lines 8-18, the function sayNotes(), says all the notes to the owner, using a loop for.
Now, the events in the "default" section.
In state_entry, line 24, we start listening to the channel.
We want to listen only to the owner. We use the id of the owner, returned by llGetOwner(), in the third parameter of llListen(),
In listen, line 29, we call the function to add a new note, with the message as parameter.
The function addNote() has only one line. We could have placed the line in the event listen instead of creating a function.
But it's a good practice to use functions instead of writing the code inside the event.
It's more clear to have the code inside functions, with a descriptive names, instead of having the events with plenty of code.
And functions with very few lines of code, could grow more and more as we improve the script with new options.
In lines 34-37, we check if the toucher is the owner and call the function to say all the notes.
We use llDetectedKey(0) instead of a loop for with total_number.
We will often do this when the possibility of several people touching the object at the same time is low and is not critical to lose a touch.
And we don't reset the script in line 42, because we want that our notes stay stored, until we have organized them and decide to delete them.
HUD
Having to rez the object to take notes is not a good idea. We will use it as a HUD.
Take your object with the script to the inventory.
In the inventory, right-click on the object, in the floating menu move to "Attach To HUD", and in its floating menu choose "Center".
It will appear in the center of your viewer as a big square (if your object is a box) or some other shape depending on the object.
Now you can right-click and edit it, in the same way than editing a rezzed object.
Change its size to make it smaller and move it to one side of the viewer. If you change its shape to an sphere, it will show as a circle.
Answers to Questions
Can we avoid the "hello avatar" message each time that we add a new script?
- A handy trick, at least if you have Firestorm, is to create your own default script to your tastes and then drop it in here: secondlife:///app/openfloater/preferences?search=Embed .
- That will make it so every new prim you rez already has your personal default script in it ready to go and you don't have to spam the world with the silly LL new script version's chat.
Do we use llGetOwner() in the function llListen?
- Yes, this way we filter the messages by the owner id, and we will only receive the messages said in the channel by the owner:
- llListen(channel, "", llGetOwner(), "");
- This is more efficent than checking the sender of the message in the event listen.
To say all the messages that we have stored, the index in llList2String has to be -1?
- No, we can only get the messages one by one with llList2String().
- We need to use a loop to say all of them.
- A negative index means "starting by the end". The index -1 is the first element by the end, so the last element:
- llList2String( quickNotes, -1 ) returns the last elements in the list.
How do we delete the notes stored in the object?
- We are not deleting the notes with the script, to keep it simple.
- The way to delete the notes is to reset the script.
How can I say the notes, one in each line, but without the timestamp and the name of the object?
- To avoid repeating the name of the object, we need to say all the notes in only one llOwnerSay().
- We need to use a new variable like string allMessages;
- In the loop, instead of saying the notes, we add them to allMessages, with a new line character at the end:
- allMessages = allMessages + llList2String( quickNotes, index ) + "\n";
- But there is a problem, llOWnerSay() only says the first 1024 characters.
- If allMessages with all the notes is longer than 1024, it will be cut.
- We need to check, before adding a new note to allMessages, if it is going to be longer than 1024. If so, we say allMessages, set it to the current note in the loop, and go on.
Is 1024 also a limit for llSay?
- All the functions to say (llSay, llWhisper, llShout, llRegionSay, llRegionSayTo, llOwnerSay) have the same limit of 1024 characters.
Saturday class
Practice solution review
-> Object 2 Quick Notes (a light blue box) -> Script 2 quick notes
In line 0, we define a variable with the number of the channel.
We could just use the number instead of a variable as we have been doing before.
But it's a good practice to use names instead of fixed values.
Because:
- It's easier to remember, when we are using the value in several places, especially when going back to work on the script after some time
- It's more clear when reading the script. We could be using several channels and is more difficult to know which number is which channel.
- It's easier to change the value, especially if it is used in several places. We only need to go to the start of the script and change it.
Perhaps it doesn't mind for this script, but it's good to start doing it. It will be useful when we write longer scripts.
We are writting the name of the channel all uppercase, with a _ to separate the words.
By convention, it means that this value is constant. We set it at the start, and it will not be changed.
Other languages has a way to define constant values, but not LSL. So we use the "all uppercase" convention.
In line 2 we define the list, quickNotes, to store the notes.
In lines 4-6, the function addNote(), adds a new note to the list.
We will call this function from the event listen, with the received message as parameter.
In lines 8-18, the function sayNotes(), says all the notes to the owner, using a loop for.
Now, the events in the "default" section.
In state_entry, line 24, we start listening to the channel.
We want to listen only to the owner. We use the id of the owner, returned by llGetOwner(), in the third parameter of llListen(),
In listen, line 29, we call the function to add a new note, with the message as parameter.
The function addNote() has only one line. We could have placed the line in the event listen instead of creating a function.
But it's a good practice to use functions instead of writing the code inside the event.
It's more clear to have the code inside functions, with a descriptive names, instead of having the events with plenty of code.
And functions with very few lines of code, could grow more and more as we improve the script with new options.
In lines 34-37, we check if the toucher is the owner and call the function to say all the notes.
We use llDetectedKey(0) instead of a loop for with total_number.
We will often do this when the possibility of several people touching the object at the same time is low and is not critical to lose a touch.
And we don't reset the script in line 42, because we want that our notes stay stored, until we have organized them and decide to delete them.
Strided lists
In LSL is not possible to have lists of lists. A strided list is the closer to it that we can get.
A "stride" is like a sub-list with several different elements. For instance:
list planets = [ "Mercury", 58, 0, "Venus", 108, 0, "Earth", 150, 1, "Mars", 228, 2, "Jupiter", 778, 79, "Saturn", 1427, 83, "Uranus", 2871, 27, "Neptune", 4497, 14, "Pluto", 5906, 5 // Dwarf planet ];
This is a strided list with a stride of 3: planet name, distance to sun, quantity of moons.
All the strides must have the same elements, in the same order.
The first stride (Mercury) has indexes 0, 1 and 2. Venus has 3, 4 and 5, and the Earth name is in index 6, etc.
Let's see how it works with the script of touchers and chatters from Monday.
Say touchers and chatters with strided lists
-> Object 3 Strided List of Touchers and Chatters (a red box) -> Script 3 list of touchers and chatters
( lines 0-6 )
The lines from the previous script are commented out, so we can compare with the strided list version.
Instead of four lists, lines 0-6,, now we have two strided lists.
They have a stride of 2, with the elements "id" and "count".
( lines 8-27 )
In addTouch() lines 10-12, instead of one index, we are using two: the index for the id, and the index for the count.
We look for the index of the id in the new list, line 14.
If there is a new toucher, we add the two elements (the id of the toucher, and 1 to start counting) to the strided list.
We add them at the same time, between [ and ] as a list.
If we are counting another touch, we set indexCount in line 21, that is always indexId + 1, the element next to the toucher id.
In lines 22-23, we get the count and increment it.
And in line 25, we set the new count to the strided list, in the indexCount position.
The function addListen() has the same changes.
( lines 50-72 )
Let's go to sayInfo(), lines 50-92.
We get the total of touchers in line 57.
But now the list has two elements for each toucher, so we divide the total by 2, because the list has a stride of 2.
In the loop for, lines 66-70, to get the index in the strided list, we multiply by the stride, 2.
In line 67, the number of toucher multiplied by 2, i * 2 , is the index of the toucher in the strided list.
In line 69, the next index from the toucher, i * 2 + 1 , is the index of the count.
To say the listeners, lines 72-92, the changes are the same ones.
No changes in the events. We are using functions for all the process, so no need to work on the events.
Strided lists with contants
-> Script 4a strided lists with constants
With a stride of only 2, it's easy to remember what is each position, but we could have strides of 5, or 10...
Let's look to a way to organized it, so we don't need to remember stride position numbers, in "Script 4a strided lists with constants".
In this example we have a lists of fruits, lines 2-18.
The list has a stride of 3: fruit name, fruit color and fruit weight (in grams).
The solution is to use constants.
LSL hasn't got constants but we use variables with names in uppercase as a convention, meaning that we will use these variables as contants.
The constants for the stride list are in lines 20-23.
First, line 20, is the elements in the stride.
Then, lines 21-23, are the names of the elements in the stride, with its position in the stride, starting with 0.
We will use these contants to manage the strided list, instead of using numbers.
We have an example function in lines 25-44.
fruitsAverageWeight() returns the average of the weights of all the fruits.
The function, in line 25, returns a value of type "float", a number with decimals. The average probably will have decimals, so we can't use an integer.
The loop on the list is the same than in the previous script, but using constants instead of fixed values.
We use FRUITS_STRIDE in line 32 to get the total of fruits, and again in line 37 to get the index in the list for the fruit.
And we use FRUITS_WEIGHT in line 38 to get the index for the element in the stride.
The if ( totalFruits > 0 ) in line 34 is necessary to avoid a division by zero in line 40.
-> Script 4b strided lists with constants
Now, let's look at what happens if we add a new element to the stride, , in "Script 4b strided lists with constants".
In this script the list of fruits has a stride of 4. We have added the diameter (in centimeters) in the third position (index 2) of the stride.
In line 20, we change FRUITS_STRIDE to 4.
We add FRUITS_DIAMETER in line 23, and we change the position of FRUITS_WEIGHT from 2 to 3, in line 24.
We don't need to change anything in the function fruitsAverageWeight(). It's the same function, copy-pasted from the other script.
fruitsAverageWeight() is based on the constants, so when the constants change the function goes on working well.
Next week
Next week we will study floating texts.
How to add a floating text, alternate between two texts, change the color every second, use a listener to change the text, and use a HUD to have a floating text on ourselves.
Answers to questions
Can we use two channels, defining each one with a different channel name?
- Yes, we can use two or more channels, with a llListen() for each one in state_entry, and adding a "constant" to give a name to each channel is a good practice.
- There is only one event listen for all the channels. We receive all the messages in this event, and it has the parameter "channel" to know from which channel the message comes.
- If we have several channels, in the event listen, we need to check the parameter "channel" to know from which channel have we received the message, using several "if" to do different things depending on the channel.
If we are carrying the list object as a hud, does it lose the info if we detach and derez it?
- It does not lose the info, even if we detach it, or derez it if it is rezzed.
- If you are using the object as a HUD, we can detach it and the info will stay there when we attach it again.
- When we detach the HUD, the script stops, so it will not record any new notes, but the data stays there.
- The data is lost when we reset the script, manually or with llScriptReset(), or when we save it in the editor after modifying it.
How does it remember the items that we have added to the list?
- The script has a memory space for data, up to 64k.
- The variables and the lists are stored there, and the memory is saved with the script when the object derezzes or detaches,
and also if the object is rezzed and there is a sim restart.
Could we put two strings in a single llOwnerSay function?
- No, we can only put one string in llOwnerSay, but we can concatenate several strings like llOwnerSay( string1 + string2 + string3 );
- llOwnerSay() can say strings up to 1024 characters. Longer strings are cut.
What happens if we accidentally remove one single element in a strided list, for example "Earth" ?
- If we remove one element in the list, not the full stride, the list will have no sense any more.
- We need to be careful when adding or removing from a strided list, always doing it with the full stride.
Is the "stride" the number of elements for each item?
- Yes, the stride is the elements that we are repeating every time, 3 for the planets example.
Is it possible to store a list in one file and the rest of the script in another file?
- Yes, we can have several scripts in the same object, and we can split the code and data among several scripts, we will study it in future classes.
What does "the first stride (Mercury) has indexes 0, 1 and 2" mean?
- A strided list, like the list of planets, is a list as any other list, with indexes starting with 0.
- "Mercury" is in the index 0 of the list, "Venus" in the index 3, etc. Index 1 is "58", the distance from mercury to the sun and index 2 is "0", the moons of Mercury.
Could we get the names of the planets with 0, 3, 6, 9, ... ?
- Yes, index 0 - 3- 6- 9... are planet names, 1 - 4 -7 - 10... are distances and 2 - 5 -8 - 11... are moons.
Is it like row and column and each row has 3 columns?
- Yes, in some way, but in this case the list has only one row with groups of 3 elements in this case, one group of 3 after the other.
If it was of a strided list of 5, would planets be 0, 5,10,15,20, and then distances, 1,6,11,16,21?
- Yes, if we add two more elements to the stride, for insance the diameter of the planet and and its color, the stride will be 5, and the planets will be at the indexes 0, 5,10,15,20, ...
What if we made a list for every stride? What if each element was a list?
- It's not possible, we cannot have a list inside a list in LSL, the elements of a list cannot be lists.
What if we store the name of the planets in one list, the distances in a 2nd list and the moons in a 3rd?
- Yes, this is good, these are parallel lists, it's the way that we used in the script on Monday.
Are parallel lists better or worse than strided lists?
- To use parallel lists or strided lists is usually a matter of preference.
Is it difficult to sort the lists?
- There is a function to sort lists, and it works with strided lists, but not with parallel lists, so this is a case when using strided lists is better.
Could we sort the list of planets from a to z a, or according to the distance?
- Yes, we can sort them using any of the elements in the stride, in ascending or descending order, we will see it in a future class.
Does LSL works with SQL?
- No, LSL doesnt work with SQL (SQL is a language used with many databases).
- There are no DBMS tools directly in LSL, but we could use HTML to communcate with an external database and use its SQL tool via the database API. That's a pretty advanced topic though. We will get into HTML communication in later lessons.
Is it important to use constants?
- Yes, some day we could have strided of 10, used in 50 places in the script, so using constants like this is very recommended, and it's better to get used to them from the start.
- Using constants in this way is a fundamental principle of good programming. Always specify the same value once whenever you can. By using constants instead of hard coding values like index offsets, if you change the offset, you change it in one place once rather than having to search your code for that specific number and then decide each time, is that my offset or some other use of the same value?
Could we do "average weight of orange fruits"?
- Yes, we need to add an "if" in line 39 to check if the color of the fruit is "orange", and also a counter of to know how many orange fruits we have.
- It's a good idea for homework: add a parameter "string color" to the function fruitsAverageWeight() and return the average of the color, or the total average if the parameter is "".
Can lists be exported to xml ? or to any other file type?
- No, LSL can export anything to files, there is no way to write a file.
- We can export data using http or sending an email.
- We will study http much later in the classes, in meanwhile, we can export data sending an email to us:
- the function is llEmail() and it has 3 parameters of type string: address, subject and message.
Is all the inner loop executed in each step of the outer loop?
- Yes, for each step of the outer loop, the inner loop executes all the steps.
- With the outer loop from 0 to 4, and the inner loop from 0 to 9, the instructions in the inner loop will be executed 50 times.