User:SuzannaLinn Resident/ScriptingClasses/Basics 3 - Lists

From Second Life Wiki
Jump to navigation Jump to search

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

(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.