User:SuzannaLinn Resident/ScriptingClasses/Basics 2
Basics 2
Monday class
Function review
-> Object 1 Say Previous Toucher (a blue cylinder) -> Script 1 say previous toucher
Let's start with a review of the format that we use when creating a new user function.
In last class we created this function:
- string getFullName(key personId)
First we have the type of the value returned by the function. In the case of functions that don't return a value, they don't have a type and we start with the name of the function.
Then it comes the name of the function, the name that we have chosen for it.
And, after the name, the parameters, between parentheses and separated by commas. In this case the function has one parameter, of type key.
So we have a function called getFullName, that needs one parameter of type key, and returns a string.
And what is personId?
We are going to use this parameter in the code of our function. We need to give a name to it. The name that we choose.
This name is only internal to the function. We are using personId in the function to get the display name and username. If we try to use personId anywhere else in the script we will get an error when saving.
When we call the function, the name that we use as a parameter changes depending on where we have the key, like:
- getFullName( id )
- getFullName( llDetectedKey( 0 ) )
- getFullName( "0f16c0e1-384e-4b5f-b7ce-886dda3bce41" ), using a literal UUID (keys have to be enclosed between quotes like the strings).
Please rezz the object "Object 1 Say Previous Toucher" from your class materials folder. It's a blue cylinder.
We are improving on the the scripts of last week.
Currently our script says who touches it. We want it also to say the name of the previous toucher.
Please touch the cylinders of the students around you and also your own one, to see how it looks.
The first time it says "() touched me and now... etc" because there isn't a previous toucher. Later, we will make this look better.
Let's see how we can do it in the script.
We get the current toucher with llDetectedKey(0). But, how do we get the previous toucher?
There isn't any LSL function that returns the previous toucher. No way to get it; the information about the previous toucher is lost.
But we are clever scripters, and we have a plan:
- we know the current toucher.
- next time that someone touches, we will need this current toucher, to say who was the previous toucher.
- we need to keep the current toucher now, for future uses.
And how do we keep the current toucher?
We can create our own named storage locations to hold the data.
They are called variables. This name means that we can change its value as often as we need in our scripts.
Each variable has its type. It can be of type integer, string, key and other types that we will see in future classes.
And each variables has its name, a name of our choice.
Same as functions, the name has to start with a letter, and after it, letters and numbers. Letters can be lowercase or uppercase. When we use our variable we will use exactly the same name, and the same lowercases and uppercases.
Variables look very similar to the parameters in the functions and events that we have been using.
A variable can only hold values of its type. Other programming languages have variables that can take any kind of value, but not LSL.
It helps to make the code easier to read and easier to check and optimize when it is saved.
As we said with functions, It's very important to choose a name that tells what data is the variable storing. This way, we will be able to read our scripts more easily.
Let's look again at the several formats of naming; all of these are good and used by scripters:
- lastToucherId (our example)
- kLastToucherId (the "k" shows that this is a variable of type "key")
- LastToucherId (similar to our example, but the first letter in caps too)
- last_toucher_id ("_" instead of caps to differenciate words, so caps are not needed)
- k_last_toucher_id (with "_", and "k" at the start)
Please open the "Script 1 say previous toucher" that is in your class materials folder.
Let's look at what we have added to our script.
Our new variable is created in line 0.
It has the type "key", because we will use it to keep the UUID of the toucher.
I have chosen the name "lastToucherId". You can choose the name that you prefer.
And you can use names as long as you like. Names don't use script memory when the script is saved.
Variables, like functions, are placed before the keyword default {}.
We could place the variables first, or the functions first, or all of them mixed.
For clarity, it's better to place all the variables first. This way we will see all of them together at the top of the script.
Now that we have created a place to store the toucher, let's go to touch_start, lines 19-23, to see how we use it.
The say function, line 21, is saying two names, using our function getFullName() twice.
The second getFullName() is called with llDetectedKey(0), as before.
We call the first getFullName() with our variable lastToucherId.
But we haven't stored any value in lastToucherId yet. What happens?
The variables are created with an empty value. This value is "" for types string and key, and 0 for type integer.
We are sending the value "" to getFullName(). Our function uses "" to call llGetDisplayName() and llGetUsername() and both functions return "".
So getFullName() is returning "" + "(" + "" + ")". Which is that message starting with () that we get the first time that we touch the object.
After saying the touchers, it's time to save the current toucher for future uses. We will assign the current toucher, that is in llDetectedKey(0), to our variable lastToucherId.
To assign a value to a variable we use the equal sign: =.
The = is always assigning something to a variable. It's never comparing. To compare values we use another sign; we will see it later.
In line 22 we assign the current toucher to lastToucherId:
- lastToucherId = llDetectedKey(0);
Next time that touch_start is triggered, lastToucherId will be ready with the previous toucher to be said.
Variables
-> Object 2 Variables (a yellow box) -> Script 2 variables
Please rezz the object "Object 2 Variables" from your class materials folder. It's a yellow box.
Now that we know how to use variables, we can make some improvements in the script to see more variables in action.
It also helps make the script easier to read. Instead of calling functions with functions as parameters, we use variables to write it step by step.
We have seen that we can add variables at the start of the script. We can add variables inside the functions too.
The variables that we create in a function can only be used inside the function itself. The variables created at the start of the script can be used anywhere in the script.
The variables in a function are created each time that the function is called, and destroyed each time that the function returns.
It might seem that we are losing much scripting running time doing it. We can call a function hundreds of times, and its variables are created and destroyed the same hundreds of times.
But this process is optimized when the script is saved, in a way that it doesnt take any time.
And we save on scripting memory. Only some variables exist at the same time. They take much less memory that all the variables existing at the same time.
It also helps the script readability. We have the variables only where we are using them.
Please open the "Script 2 variables".
There are changes adding variables to most of the functions.
Let's start with getFullName() in lines 2-14.
We have added 3 variables (lines 4-6): displayName, username and fullName. All are of type string.
We use displayName and username to store the names returned by the LSL functions (lines 8-9).
And we use fullName to store the names (line 11) to return (line 13).
Now to the event listen, lines 23-30.
There is the variable fullName (line 25), that we use to store the name (line 27) to say (line 29).
And finally the event touch_start, lines 32-45.
There are 3 variables (lines 34-36). A key for the current toucher and two strings for the names of the current toucher and the last toucher.
We assign their values in lines 38-40.
Now we can write a cleaner say function in line 42.
And assign toucherId to lastToucherId in line 44, instead of using llDetectedKey(0).
Now, in this function we are calling llDetectedKey(0) only once instead of twice.
Using variables we avoid repeated calls to LSL functions. To call an LSL function is much slower that getting a value from a variable, so we save scripting time.
And, why haven't we moved the variable lastToucherId into the event touch_start?
It would not work.
The variables in the event are destroyed each time that the code in the event finishes to execute.
If key lastToucherId is inside the event touch_start, then:
- we assign toucherID to lastToucherId (line 44).
- the event code finishes; the variables are destroyed.
- on the next touch, the event is triggered again; the variables are created.
- our variable lastToucherId, recently created, has a value of ""; we can't say the previous toucher name.
If
-> Object 3 If else (a green prism) -> Script 3 if else
Please rezz the object "Object 3 If Else" from your class materials folder. It's a green prism.
Let's go solve the problem with that empty previous toucher in the first time we touch.
Touch your prism and the prims around you to see that the first time it only says the current toucher.
The previous toucher name is only said if there is a previous toucher.
We can use the language keyword "if" to write code that will be only executed in some conditions.
The format is:
if ( condition ) { // code to execute if the condition is true }
In our script we want to add the previous toucher name when the variable lastToucherId is not empty.
The empty value is "", so we check if lastToucherId is different, or not equal, to "".
The LSL operator to do it is !=
And the if that we use is:
if ( lastToucherId != "" ) { // code to execute is the condition is true }
Please open the "Script 3 if else".
The event touch_start is in lines 39-60.
We have added a string variable, messageToSay in line 44. We use this variable to build our message in different steps.
In lines 49-53 there is our if condition.
Lines 51 and 52 are only executed when lastToucherId is not equal to "". Otherwise the variable messageToSay stays empty.
In line 55 we always add the current toucher to messageToSay.
If else
Another improvement in the script is that when the display name and the username of a toucher are the same, we don't want it to repeat it twice.
For instance, in case that I had changed my display name to SuzannaLinn, saying "SuzannaLinn" instead of "SuzannaLinn (suzannalinn)".
Two strings are equal only when they have the same text in the same case. So "SuzannaLinn" is not equal to "suzannalinn".
The username is always in lowercase. We will change the display name to lower case before comparing tthem.
There is the LSL function llToLower() to do it. It expects a string and returns a string.
We are doing it in the function getFullName(), lines 2-21.
We add another variable, of type string, to store the display name in lowercase: displayNameLowerCase, in line 5.
We get its value in line 10:
- displayNameLowerCase = llToLower(displayName);
The conditional part is in lines 13-18.
In the condition of the if we use the operator == to check if username is equal to displayNameLowerCase:
- if ( username == displayNameLowerCase )
The double == is the operator to compare for equality.
Important!!!
- Double == compares.
- Single = assigns to a variable, never compares.
If we use = instead of == in the condition of an if, often we will not get an error when saving the script, but the script will behave very strangely in some cases.
It's a very common error, especially if you are used to other programming languages that haven't got the ==.
And usually it's an error difficult to find. If your script does unexpected things, remember to check your ==.
In line 15, if the username is equal to the display name, we assign only the display name to the variable fullName.
If they are not equal, we want to do as before, returning both names.
We could add another if, checking for not equal, like:
- if ( username != displayNameLowerCase )
But there is another LSL keyword that does exactly this, the keyword "else".
The format is:
if ( username == displayNameLowerCase ) { // code if the condition is true } else { // code if the condition is false }
Both keywords, if and else, have their own block of code, enclosed in { and }.
One or the other of the blocks will be executed.
We have the else in line 16, and in line 17 we assign the names to the variable fullName, when the names are not equal.
Answers to questions
Is the previous toucher identified in the message from the current toucher?
- No, we have to get the previous toucher in the previous message, and store the UUID of the previous toucher.
Is there an advantage to using one style of naming variables to another?
- There is no advantage to using one style over another, choose the one that you feel more comfortable with, but always use the same style.
If we only use the "k" as a prefix for key values, what do we use for other types?
- We use "s" for strings, "i" for integers, etc.
Could we define the key lastToucherId = " no one touched me"; ?
- No, because it's a variable of type key, and it can only contain a key, a UUID.
Do we always need am event state_entry?
- No, if we don't need to do anything in the state_entry, we don't have to write it as empty.
- No event is mandatory, but we need to write at least one event, any event.
If we didn't have the state entry in this script, where would the llListen function go?
- We can place the function llListen in any other event, except within the event listen itself
- For instance, we could say a message: "touch me to start listening" and place the function llListen in the event touch_start.
- Never place it in the event listen, if we don't start listening to a channel, the event listen will never be triggered.
Why can't be use variables that are inside another function or another event?
- All the variables defined inside an event or a function, and also their parameters, can only be accessed from within that event or the function.
- If we want to access a variable from multiple places, we need to define it as a global variable outside of any function or event.
- If you use it only within an event or function and don't need to store the value between calls to the event or function then make it local, only use globals to store values that need to be kept between calls, among calls to different functions or events.
When an event finishes executing, does the variables that are created at that level count against memory?
- No, when the event or function finishes, the variables disappear and stop using memory.
- It's good to define as many variables as possible inside events or functions to save memory.
- Local variables are stored on the stack when possible, while globals go in the heap. Stack references are always faster than heap references and are much easier for the memory manager to delete when they go out of scope and are no longer needed. This is true of nearly all languages, not just LSL.
In case of gothic names, they are shown with display name?
- Yes, any special character is shown in the display name, gothic, chinese, arabic, symbols...
When an "if" doesn't have an "else", ff the condition specified isn't true, it just does nothing?
- Yes, the "else" is optional, if the condition is false it jumps to the code after the closing brace of the "if".
Using the if/else if/else tree is the "LSL best practice?
- In general using a chain of if...else if... else statemetns to check for different cases is good practice.
Do we always have to use return when we exit the function ?
- Yes, if we have defined the function to return a value.
^ If it is, for instance, a string function, we need to use "return" to provide a string before the function ends.