Difference between revisions of "User:SuzannaLinn Resident/ScriptingClasses/Linkset Data"
Line 621: | Line 621: | ||
* So it can find "hello world" if "hello" is at the end of a line and "world" is at the beginning of the next line. | * So it can find "hello world" if "hello" is at the end of a line and "world" is at the beginning of the next line. | ||
* In this case it returns the line where the match starts, and if the length of the match is longer than the rest of the line, it means that it goes on in the next line, or lines. | * In this case it returns the line where the match starts, and if the length of the match is longer than the rest of the line, it means that it goes on in the next line, or lines. | ||
== Saturday class == | |||
=== Saturday class === | |||
=== Reading all notecards into the linkset data === | |||
Please rezz the "Object 5 Reading all the Notecards" that is in the Class Materials folder. | |||
The script has the same structure than the Script 3 reading poems from Wednesday. | |||
In the linkset data, instead of poems, we have notecards. The key identifies the notecard and the line. | |||
We are doing an improvement from the other script. | |||
We were copying the name of the poem in each key, for each line, and this uses linkset data memory. | |||
Now, instead of using the name of the notecard in the key, we are: | |||
* making a list with the names of the notecards, in the script memory. | |||
* using the index in this list for the key, instead of the name. | |||
Let's look at the changes in the script, starting with the events. | |||
In state_entry we delete the linkset data, to start reading the notecards again. | |||
notecardIndex is the counter of the notecards in the contents, starting with 0. | |||
And we call notecardRead() to start reading the first notecard. | |||
touch_start calls the function to list the notecards and listen calls the function to say the notecard selected by the owner, in the same way than the other script. | |||
dataserver works the same, calling lineRead() in line 91 to write the line in the linkset data. | |||
And at the end of the notecard, lines 99-102, we increase the counter and go to read the next notecard. | |||
Let's go to the functions. | |||
notecardRead(), lines 9-18, is added in this script. It starts reading a notecard. | |||
We use notecardIndex, the counter of notecards to get the name in line 10. | |||
When there aren't more notecards, a notecard with the notecardIndex doesn't exist llGetInventoryName() returns "" and we say a message to the owner with the quantity of notecards read. | |||
If the notecard exists, lines 11-14, we add it to our list notecardNames , and we request its first line. | |||
This function is called the first time from state_entry, and after reading each notecard from dataserver. | |||
lineRead(), lines 20-25, write in the linkset data. | |||
It's easier than in the other script, because we already have the key components in global variables. | |||
We add zeroes to notecardIndex to start with "01", and to notecardLine to start with "001". | |||
Now the function to list and say. | |||
listNotecards(), lines 27-36, is also easier than before, because we already have the names of the notecards in the global list notecardNames. | |||
We loop on the list to say the names. | |||
And we say a notecard in sayNotecard(), lines 38-64. | |||
As before we check is the number of notecard selected by the user exists. | |||
We show our list to the owner starting with 1. In line 48 we decrease the number selected to be the index of the notecard in the list starting with 0. | |||
We get the title of the notecard from the list of name and the keys of the lines, starting with the number of the notecard, in lines 49-50. | |||
We say the title and loop on the keys to read and say the lines. | |||
=== Next week: Info Boards | |||
With this week 8 we have finished the basics of scripting. | |||
We will be using the contents of these 8 weeks often in the rest of the weeks, so we will be studying it once and again, seeing more examples. | |||
You need to learn well these 8 weeks to be good scripters. | |||
From now on the weeks will be focused on one scripting topic, more independent of each other. Some things we will use again in other weeks, some other things will be seen only that week. | |||
The structure of the week will change slightly. | |||
As usual, Monday are classes and Wednesday are questions, practices and sometimes some more contents, related to Monday classes. | |||
But Saturdays will be questions and practices related to the basic concepts, without new contents. | |||
Next week, week 9, we will study Info Boards. | |||
The idea is to have a board, one prim with a texture showing different options. | |||
We will detect the option touched on the board, to give objects, notecards, landmarks, links, group and contact info, etc. | |||
We will use a configuration notecard to tell the script what to do. | |||
=== Answers to questions === | |||
Why do we delete the linkset data every time? | |||
* We delete the linkset data when the script is reset and read the notecards again, to be sure that everything is updated. | |||
* No need to do it in this script, we could check it with the changed event, but it's done this way to keep the script easier. | |||
Where in the script do we reset the numbers, so that the first one isn't 0? | |||
* When we list the notecards, in line 34, we add 1 to the index, to show the list of notecards starting with 1, which is more bio-like. | |||
* It means that the user will say a number that is the index of the notecard plus 1, so we need to decrease the input from the user by 1. | |||
* We decrease it in sayNotecard() in line 48. The parameter notecard is the number that the user has said, coming from the event listen. | |||
What happens when NAK is detected in dataserver? | |||
* To read the first line of the notecard we use the async function to request it, then usually the notecard will be stored in the sim. | |||
* We read the notecard in the event dataserver, in the do...while loop, lines 89-98, using the sync function. | |||
* If a NAK appears, very uncommon but it could happens, we request the same line again, this time with the async function, in line 95. | |||
* Also if there is a NAK, we check it in the while to leave the loop, because we will go on reading when the dataserver is triggered again. | |||
* All happens in the dataserver code, the dataserver is triggered when we request the first line in the notecard and if there is a NAK is triggered again with the line. | |||
* When the notecard finishes it calls notecardRead() in line 101 to read the next notecard. | |||
* In short, the process is: | |||
** readNotecard() - line 0 - dataserver - NAK - dataserver - EOF - readNotecard. | |||
What is the function of llToUpper()? | |||
* llToUpper() changes a string to uppercase. | |||
* llToLower() changes to lowercase. | |||
* In this case we want to say the title of the notecard in uppercase. | |||
* The title is in uppercase when the poem is said, but not when the poem titles are listed. | |||
If we have 5 notecards and the script reads them all, and then we remove some of notecards and put some others, will the script update itself? | |||
* Yes, because the removing or adding of notecards will trigger the event changed and there we reset the script, delete the linkset data, and read all the notecards again, in lines 106-111. | |||
Has the linkset data limits on storage size and key count? | |||
* The linkset data has a limit on storage, 128k, no limit on the quantity of keys, only limited by the storage. | |||
What will happen if we run out of storage? | |||
* If there is no space to write the key and the value, llLinksetDataWrite() does nothing. | |||
Could it happen that when we try to write a long chunk of data to linkset data, it fails, but when we write a shorter piece at a later time, it may write sucessfully? | |||
* Yes, it could happen that one long line is not written, but a next, shorter line, is. | |||
How can we check if the data has been written? | |||
* llLinksetDataWrite() returns a value, the result of the operation: | |||
** integer error = llLinksetDataWrite(...); | |||
* "error" will be the constant LINKSETDATA_OK, which is 0, is all has gone well. | |||
* If it has run out of memory, the error will be LINKSETDATA_EMEMORY. | |||
* If we think that our script can run out of linkset data memory, it's better to use the returned value to check for it. | |||
* The list of errors and its constants is here: https://wiki.secondlife.com/wiki/LlLinksetDataWrite#Caveats | |||
* LINKSETDATA_EMEMORY is returned when there is not enough space to write the key_value pair, both together, if there is no space for both, nothing is written. |
Revision as of 07:22, 1 December 2024
Linkset Data
Monday class
Linkset data
The linkset data is a permanent storage. It belongs to the object, not to the script.
The data stays there even if the script is reset or deleted. We can reset the script, modify and save the script, delete the script, and the data will stay there.
Ir's a list of pairs (key,value). In other programming languages, this is called a "dictionary".
Both key and value must be of type string, no matter what type the data really has.
We need to typecast to string when writing in the linkset data, and tytpecast back to the type of the data when reading.
It has a storage space of 128K. Scripts has a memory of 64K each.
Linkset Data stores the string in UTF-8, using 1 byte for the common characters. Script memory uses UTF-16, 2 bytes for each character.
Often the information in the Linkset Data information uses the double of space when copied to script memory.
Each object has its own linkset data. The object, not the prim. If an object has several prims linked, there is only one linkset data.
The data is accessible to all the scripts in all the prims of the object. We can write it in one script and read in another.
The advantages of the linkset data are:
- Permanent storage
- Bigger storage
- Shared for all the scripts in the object
Now if the script is reset we lose all the information. Or if we want to do some improvement in the script, we can't, because when saving the script is automatically reset.
And if we need to store many info the script will run out of memory.
Let's take a look at the functions that we will use with the linkset data.
To write we have llLinksetDataWrite( string name, string value ).
It stores "name" and "value". The "name" works as a key.
To get the information back we will use "name" as its key.
If later we write the same "name" with a different "value", the new "value" will be stored replacing the old one.
If "value" is an empty string, "name" is not written. Instead, if "name" already existed, it is deleted.
If we want to store only keys, like a list of avatar id's whom the script has given a notecard to avoid giving it again, we need to use a value, an white space for instance, " ".
To read there is llLinksetDataRead( string name ).
It returns de value associated with "name".
If "name" doesn't exist, it returns an empty string.
If we want to check if the key exists, we also use llLinksetDataRead() and check if the returned value is "".
We can see the linkset data as a list of string variables. The "name" is like the name of the variable and we assign a value to it, like doing "name=value".
llLinksetDataCountKeys( ) is used to know how much information we have stored.
It returns how many pairs (key, value) we have in the linkset data.
llLinksetDataReset() resets the linkset data.
It deletes everything that we have stored and leaves the linkset data empty. No way to recover it.
We need to be careful when using this function.
Reading the linkset data is slower than accesing the script memory.
If we have some data, not too big, that we use often in the script, is better to read it from the linkset data in the event state_entry and store it in variables.
Example: Quick notes
Please rezz the "Object 1 Quck Notes" that is in the Class Materials.
We are improving a script from the class Basics 3.
The idea is to store quick notes written in a channel, to be listed later.
The script listens to the owner in channel 7. In the previous version we stored the notes in a list, now we are going to store them in the linkset data.
Let's look at the script.
As keys we are using correlative numbers (typecasted to string), starting with 0, the values are the notes.
We have count, In line 2, to know the number of the next key. It's faster than using llLinksetDataCountKeys() each time.
In addNote(), lines 4-8, we write the note and increment the counter.
In sayNotes(), lines 10-16, we use "count" to loop on all the keys, and say the notes to the owner.
Let's go to state_entry, lines 20-24.
We get the total of keys. The first time, with the linkset data empty, it will be 0, Next times, after a reset of the script, we will recover the count.
And we start listening to the owner in channel 7 for the notes.
Going to listen, lines 26-38.
We need a way to delete the info, once we have listed and processed them. We do it typing RESET in uppercase.
If it is RESET we say the notes for the last time and delete them.
Otherwise we add the message to the notes.
In touch_start, lines 40-45,we say the notes to the owner.
And in changed, lines 47-53, in case of a new owner everything is reset. If we give the object, we don't want that the new owner see our notes.
More on Linkset Data
There is only one linkset data in each object, no matter how many linked prims it has.
And if we link two objects that both have a linkset data?
They are merged. If there are duplicate keys, one of them is lost. If they are too big, the (key,value) pairs that don't have space are lost.
And if we unlink a prim from a object that has a linkset data?
It is linked to the root prim. If we unlink a child prim, it will have an empty linkset data. If we unlink the root prim, the unlinked prim will have the linkset data, and the object will have an empty one.
Let's take a look to some more functions.
To delete a (key, value) pair we use llLinksetDataDelete( string name ).
If the "name" doesn't exist, it does nothing.
Using llLinksetDataDelete() is much more clear than using llLinksetDataWrite() with an empty string.
To get all the keys we have llLinksetDataListKeys( integer first, integer count ) .
It returns a list with the keys, starting at the index in "first" and returning a quantity of "count" keys (or less, if it arrives to the end of the linkset data).
Then we will loop in this list of key to get its values with llLinksetDataRead().
Why not returning all the keys at once? Because the linkset data storage is much bigger than the script memory, and sometimes we will not have enough free memoty in the script for it.
If we are sure that we have enough free memory we can get all the keys with llLinksetDataListKeys( 0, 0 ).
The data is automatically sorted alphabetically by the keys.
We will get the list of keys in alphabetical order, not in the order that we have written them.
llLinksetDataAvailable() returns the quanity of free bytes of storage in the linkset data.
When it is empty, there are 131072 bytes.
There is no limit in the length of key and values, other than the space available in the 128k.
We have to be careful to not write very long strings, because perhaps later we will not have enough free script memory to read them.
Example: List of Chatters
Please rezz the "Object 2 List of Chatters" that is in the Class Materials folder.
This is another script from the class Basics 3.
The idea is to count how many times each person has chatted in public, to be listed later.
We used two lists (for the chatter id's and the count of messages) to store the data.
Now we are using the linkset data. The key is the chatter id, and the value is the count of messages.
No global variables this time, which doesn't happen often.
In addListen(), lines 0-8, we add a new chatter or increase the count of messages if we already have the chatter.
We get the count in line 2, as always we get a string so we typecast it to integer.
If the chatter is not in the linkset data, llLinksetDataRead returns "", that becomes 0 when typecasted to integer.
If count is 0, we add the chatter with a starting count of 1.
If we already have the chatter, we increase the count.
We need the parentheses in
- (string)(count + 1)
with
- (string)count + 1 // WRONG !!!
it would typecast count to string, and try to add 1 to the string, throwing an error.
We could replace lines 3-7 with line 6 only. It does the same. We are detailing the process to show it more clear.
In sayChatters(), lines 10-22, we say all the chatters with their count of messages.
Now we don't have the keys from 0 to count. We have irregular values in the keys, which is the usual situation.
We get the list of keys in line 15 with llLinksetDataListKeys(0, 0).
We are reading all the keys together. We would get a script memory error with about 1,000 keys. But this is not expected to happen in this kind of script.
In other situations we would need to get the keys in chunks, of 500 keys, for instance:
- llLinksetDataListKeys(0, 500) // starting with key index 0, 500 keys
- llLinksetDataListKeys(500, 500) // starting with key index 500, 500 keys
etc, of course doing it in a for loop.
We loop on the keys, lines 18-21, to get each key in personId, and read the value with llLinksetDataRead.
getFullName(), lines 24-33, is the function that we used in the script in class Basics 3 to get the name.
It's a shortened version, you can compare it with the previous one.
The events are doing the same than in the script quick notes.
state_entry starts the listener, now listening to everyone in the public channel.
listen calls addListen() to add a new chatter or increase their count, or deletes the linkset data if the owner has said RESET.
touch_start list the chatters to the owner.
And changed resets everything in case of a new owner.
Answers to questions
Is it possible to have array of dictionaries in LSL?
- No, it's not possible in LSL, we will need to wait for Lua
What's the difference between an Object property and a Prim property?
- Object properties are shared by all the prims
- Prim properties are only for the prim, like the prim name, for instance
- Most of the properties are prim properties, color, size...
Storing in the linkset data, when the object is re rezzed it is lost?
- No, storing in the linkset data is never lost, this is one of its big advantages.
Can we store a set of individual notes and then read them all back as long as the object is rezzed?
- Yes, we can store lines of text, strings, each one with a different key, and we can read them at any moment, if the object is rezzed.
How do we know when it's reaching the storage limit?
- There is the function llLinksetDataAvailable() that returns the quantity of free bytes in the linkset data.
What kind of data is the linkset data usually used for?
- For all kind of data that comes in big quantities, there are plenty of uses, for instance:
- storing messages, info about people coming and going
- reading notecards and storing in linkset data
- statistics, visitor counters
- store user preferences
- Anything that we can store in a variable can be stored in the linkset data
Can we retrieve the list more than once, as the data accumulates?
- Yes, we can read the linkset data as many times as we want.
Is linkset data similar to global variables, with the difference that they stay alive after derezzing the object?
- All of them stay after derezzing, but globals variables disappear when resetting the script.
- Linkset data is even more global, because other scripts in the object have access to it.
- For instance, one script can read the config notecard and other scripts can get their configuration data without any message passing at all, its a way to exchange information among scripts.
Can we delete just one note ? Or all or nothing?
- Yes, we can delete only a note, a key in the linkset data.
If we take back the script into the inventory does it delete the data?
- If we remove the script from the object, the linkset data stays, if we add the script again to the object, we can list the notes.
Can we add other commands as well, like "RESET" in this script?
- Yes, we can listen for other commands and write a chain of if else if else if to check all the commands and do different actions.
Do we have in LSL something like 'case', or do we have to use many ifs?
- There is no "case" or "switch" command, only ifs.
If we're all using the same channel, will we confuse the script?
- No, because the script is only listening to the owner.
Do we have to reset our notes when we give it to someone else?
- lines 47-53 are only for the case that we have notes stored and we give the object to someone else, to delete the notes and avoid the others knowing our secrets, so when the object is transfereed to someone else, the linkset data is cleared.
What would happen if we choose channel 0 for channel notes?
- It would store as notes everything that the owner says in the public channel.
- The owner would make a record of everything that the owner has said in chat, but not other people because is only listening to the owner.
Would I have to write "/0" before saying the statements?
- No, no need to use /0, it's the channel by default when we say something.
What do we need to change if we don't want to listen to owner only?
- Replacing llGetOwner() with NULL_KEY in the function llListen(), everything said in local in its hearing distance would be recorded.
In the llinkset data, do the internal separators also count as chars?
- Only the strings that we store counts for the space, the internal separators doesn't count.
Has channel 0 a range?
- The range of listening is 20 meters from the center of the object where the script is.
Can we attach it to ourself as a HUD and sort of carry it with us?
- Yes, we can.
If an object is, let's say 20x20, does that change the range?
- No, it's always 20 meters from the center of the object.
Does (string)count just converts integers into strings?
- (string) converts any tipe of variable into a string, in this case an integer.
What count = llLinksetDataCountKeys(); does?
- This function returns the quantity of (key, value) pairs in the linkset data.
If we unlink the root prim will the linkset data still work?
- If we unlink the root prim, this unlinked prim will have the linkset data, the other prims will have it empty.
What are linked prims ?
- All the prims in the linkset/object, when an object have several prims, they are linked, so they are "linked prims".
- Sometimes excluding the root prim depending on the context.
Do you mean physical linking? for example having 2 cubes and linking them together?
- Yes, with the "link" option in the edit window.
Can we give 2 values the same key?
- No, the second value will replace the first.
Could anyone else have access to my Linkset Data? Maybe by a kind of ID/UUID, same as with notecards?
- No, no way, only scripts in the object can access the object's linkset data.
How much of the linkset data can we fit in our script?
- Our script is using parts of it's 64k for data, code, and everything else. Also if we're using the common latin alphabet our data will take up twice the space with UTF-16 in script memory that it takes with UTF-8 in linkset data. This means that the most data our script can process in a chunk will be something less then 25% of the space available to linkset data even with a tiny script.
Will people talking through objects, or with translators, be listened?
- Yes, everything that appears in the public chat is, the listener doesn't distinguish between objects and avatars except by matching names or keys.
Does the object have the same key as the owner?
- No, each object has a different uuid, to know the key of the owner there's the function llGetOwnerKey().
Are avatars and agents the same?
- Agent is the proper term for our presence in a sim. Avatar is the body we happen to be wearing at the time. But we often say Avatar when we really mean Agent.
Is it also 64k with Lua?
- Lua will have also 64k of script memory, but strings are stored in UTF-8, so much often they will take less space, also the compiled code is shorter.
Where is the linkset data stored? In what server?
- It is stored in the central server, all the time, and in the region server when the object is rezzed in the region.
A script can have access to only one linkset data?
- Yes, only the linkset data of the object where the script is.
Does the linkset data have its identifier?
- No, there is no uuid for the linkset data, it just goes with the object.
Why are we storing the uuids instead of the names?
- Here we are showing names, but storing data by UUID. Most often, we will want to use the UUID for data storage because it is guaranteed to be unique. The name could easily be anyones user name or display name, which, if we were storing by name, would cause conflation of data.
Wednesday class
Reading poems
Please rezz the "Object 3 Reading Poems" that is in the Class Materials folder.
This object does the same than the Reading Poems that we studied last week, but now we read all the poems from the notecard and store them in the linkset data.
There are some changes in the notecard "Poems" to make it easy to read. Take a look at it.
The first line in each poem is the title. At the end there is a line with *** to know that another poem starts.
In the linkset data, the (key, value) pairs are be:
- as key, the name of the poem and the number of line
- as value, the text of the line
But we have only one string for each key, not two for the name and line.
So will pack them together in one string, like:
- <name of the poem> _ <number of line>
using a "_" between them.
And... it will not work. Why?
Because the info is automatically sorted by alphabetical order, and our poems would be sorted first line 1, then lines 10 to 19, then line 2, lines 20 to 29, line 30...
So we will add zeroes in front of the number, like this:
- "My Poem_001"
To read the keys from the linkset data, instead of llLinksetDataListKeys(), we will use:
- llLinksetDataFindKeys( regex pattern, first key, total keys )
The first parameter is a regular expression to search in the keys. Only the keys where the regular expression is will be returned.
"first key" and "total keys" are the same parameters than in llLinksetDataListKeys():
- the number of the first key to return (counting only the keys where the regex is found).
- the total of keys to return.
The regex pattern is only searched in the keys, not in the values.
Two of the regex instructions are very useful with the linkset data:
- ^ to select keys that start with something, like: "^My poem" , returning all the lines in the poem.
- $ to select keys that end with something, like: "000$" , returning the lines number 0 of all the poems.
There is another function:
- llLinksetDataCountFound( regex pattern )
that returns the quantity of keys where the pattern is found.
Let's go to see the script, starting with the events.
In state_entry, lines 71-76.
We use poemTitle to know what poem we are reading in the notecard, we initialize it to "".
We start listening to channel 1, as we did in the example of last week.
And we request the first line in the notecard Poems.
Today we will read all the notecard at the start and store it in the linkset data.
The maximum size of a notecard is 64k, so it will fit in the 128k of the linkset data.
In touch_start, lines 78-81, we call listPoems() to say a numbered lists of all the poems. We will see this function in a while.
In listen, lines 83-87, we call sayPoem() with the message, that should be a number, typecasted to integer. We check if this number is ok in the function sayPoem().
dataserver is in lines 89-105.
We read the full notecard with the same code that we studied last week.
In line 93 we call lineRead(), for each line of data, to add the line to the linkset data.
In line 102 we say a message when all the notecard is read.
In changed, lines 107-113, we empty the linkset data and reset if the notecard is changed or there is a new owner.
Let's go to lineRead() in lines 9-23.
First we check if the line is ***, meaning that this poem has ended and another is going to start.
In this case we set poemTitle to "" and do nothing else.
If it is not ***, it can be:
- the title, in this case the variable poemTitle is empty.
- a line, we are counting the lines in the variable poemLine.
Both variables are global, because we are using them in more than one function.
If it is the title, lines 14-17, we set poemTitle to it and poemLine to 0. The title will be stored as line 0 of the poem.
In line 18 we get the key for the linkset data, like:
- "My poem_001"
We add zeroes in front of the number:
- "00" + (string)poemLine
so the lines will be "003" or "0051" 0r "00124".
We want the last 3 digits of this result, with:
- llGetSubString("00" + (string)poemLine, -3, -1)
that returns a string from the character 3 counting by the end to the character 1 counting by the end (the last character).
In line 19, if the text of the line is empty, we change it to a white space, because we can't write empty values in the linkset data.
And we write the line and increase our count of lines.
Now to listPoems(), in lines 25-37.
In line 30 we use "$" in the pattern to select all the keys ending with "000".
We have the titles in line 0, so we get the titles of all the poems.
We loop on all the keys to say the titles to the owner.
In line 24 we get the title without the "_" and the "000", with:
- llGetSubString(title, 0, -5)
It returns the string from the character 0, the first one, to the character 5 counting from the end, included. Character -4 is the _ and characters -3, -2, -1 are the "000".
We say the number of poem adding 1, for a more natural way to start the list with 1 instead of 0.
The \t is a tabulator, it adds some spaces between the number and the title.
And finally to sayPoem(), in lines 39-67.
We get the titles in the same way than in listPoems(), in line 48.
In line 50 we check is the number said by the owner is a valid number of poem. If not we say a message in line 65.
We get the title of the selected poem, also in the same way, in lines 51-52.
We get all the lines of the poem in line 53, using "^" and the title, to get all the keys starting with the title.
We say the title in uppercase, with some empty lines for a better look.
We loop on the lines, starting with 1, because 0 is the title and we have already said it.
In line 60 we read the linkset data, and we say it.
Regular expressions
Please rezz the "Object 4 Find Text Sync with RegEx" that is in the Class Materials folder.
This is the same object that we used last Saturday. We studied the script but we didn't get time to see some regex examples. Let's do it now.
A regular expression, often shortened to regex, is a way to describe patterns in text. It’s like a search tool that helps us find, match, or manipulate specific pieces of text based on defined rules.
A regex is made up of symbols and characters that describe what you’re looking for, like "any digit," "a space," or "a word."
We can find very specific things in text, like "all email addresses" or "dates in the format MM/DD/YYYY."
The complete "Regular Expression Cheat Sheet" is here: https://wiki.secondlife.com/wiki/LlFindNotecardTextSync#Notes
Let's try some examples to see the different expressions in action.
Match all dollar amounts
- \$[0-9]+\.[0-9]{2}
Matches a dollar sign (\$) followed by one or more digits ([0-9]+), a dot (\.), and exactly two digits ([0-9]{2}).
The characters that has also a meaning as expressions, like $ or ., has to be escaped with a \.
[ and ] contains a range of characters (separated by -) or a list of possible characters (without -).
{ and } contain the quantity of characters, or an interval of quantities.
+ means 1 or more of the previous characters, * means 0 or more, ? means 0 or 1.
Find order numbers starting with #
- #\d+-\d+
Matches # followed by one or more digits (\d+), a hyphen (-), and more digits.
\d means a digit, \l is a lower case character,\u is an uppercase, \w is alphanumeric plus underscore, \s is any whitespace, tabs and newlines.
Search for email addresses
- [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
Matches valid email formats (e.g., support@example.com).
{2,} means 2 or more of the previous character, {2} is 2, {2,5} is from 2 to 5.
Match dates in the format Month DD, YYYY
- [A-Z][a-z]+ \d{1,2}, \d{4}
Matches a capitalized month name ([A-Z][a-z]+), followed by one or two digits for the day (\d{1,2}), a comma, and a four-digit year (\d{4}).
Find times in the format HH:MM AM/PM
- \d{1,2}:\d{2} (AM|PM)
Matches one or two digits for the hour (\d{1,2}), a colon, two digits for minutes (\d{2}), and either "AM" or "PM."
( aaaa | bbb ) means either aaa or bbb.
Extract all product model numbers
- [A-Z]{2}-\d{3,4}
Matches two uppercase letters ([A-Z]{2}), a hyphen, and 3 to 4 digits (\d{3,4}).
Capture name prefixes like "Mr." or "Ms."
- (Mr|Ms|Mrs|Dr)\.
Matches titles like "Mr.", "Ms.", "Mrs.", or "Dr." by using a group (( )) and the | for alternation.
All words starting with "A" or "a"
- \b[aA]\w*\b
\b: Matches a word boundary, ensuring the match is at the start or end of a word.
[aA]: Matches an uppercase or lowercase "A".
\w*: Matches zero or more word characters (letters, numbers, or underscores).
\b: Matches the boundary at the end of the word.
Extract all phone numbers
- \(\d{3}\) \d{3}-\d{4}
Matches phone numbers in the format (555) 123-4567. It uses \(\d{3}\) for the area code and \d{3}-\d{4} for the rest.
Match all prices
- \$[0-9]+\.[0-9]{2}
Matches lines with a $ (\$) one or more digits ([0-9]+) a point (\.) and two digits (0-9]{2}).
Lines with items ordered
- ^blank:*\d+\.[^$]*\$\d+\.\d+
and : contains a named type, blank is any space character except a newline. Other named types are "alpha", "digit", "control", etc.
^ at the beginning of the expression means "lines starting with...", $ at the end of the expression means "... is at the end of the line"
[^ and ] means any character except this, [^$] is any character except the $.
Answers to questions
Is the changed event executed before or after state_entry?
- It's executed when there is a change on the object, in this case editing the notecard or giving the object to someone else.
With llLinksetDataReset() everything in the linkset data is lost?
- Yes, we are deleting all of it with llLinksetDataReset()
If we take the object back in inventory, change the notecard, then rez again; will the script detect the change of the notecard?
- We can't change the contents of the object while it is in the inventory.
- We need to rezz or attach it, and then the script will be running and will receive the event changed.
Can we edit it, adding it to us?
- Yes, added as attachment or as HUD the object is rezzed, the script is running, we can edit it, and it will receive the event changed.
What happens when the linkset data is full?
- The (key,value) is not written and the script goes on.
Can we still retrieve the data when is full?
*Yes, the new data is not added, and all the previous data stays there and we can retrieve it as usual.
But since the notecard information isn't unlimited, that shouldn't happen?
- Notecards can store 64k, and the lnkset data has 128k, so, in this script running out of space is not expected.
Can we use the linkset data to store multiple notecards?
- Yes, we can read several notecards and store all of them in the linkset data, as long as the added size of all notecards is below the 128k limit of the linkset data.
Can we store two full notecards in the linkset data?
- We can't store two 64k full notecards, because we have to count also the space of the keys.
Can we access 256k memory?
- No, only 128k of linkset data, only 128k for all the object.
- We could have several objects, exchanging information saying messages to a channel, to store 128k in each object, but it gets complicated.
Bearing in mind that the sim buffers notecards, if we're trying to access more notecard data than will fit in linkset data, might we be better off depending on the sim buffering and reading from multiple notecards directly?
- Yes, we are reading notecards as a way to practice with the linkset data, but its not always the best solution.
- With this example of reading poems we have the two options, finding text in notecards, or using the linkset data.
- Depending on the script one solution will be better than the other.
What determines the title of the poem?
- The script assumes that the first line in the poem in the notecard is the title.
How can we not differentiate upper from lower, making a case insensitive search?
- Adding (?i) at the start makes the pattern case insenstive.
- The (?i) marker makes the whole search case insensitive in LSL, and according to the documentation must be at the beginning of the line. This is an instance of LSL's partial implementation. In the full spec you can mark portions of your search case-insensitive and other case-sensitive.
Can we search through lines¿
- Yes, llFindNotecardTextSync searches for a match on the whole notecard at once, ignoring line endings. Normally regex searches are line by line unless we specify multiline searches.
- So it can find "hello world" if "hello" is at the end of a line and "world" is at the beginning of the next line.
- In this case it returns the line where the match starts, and if the length of the match is longer than the rest of the line, it means that it goes on in the next line, or lines.
Saturday class
Saturday class
Reading all notecards into the linkset data
Please rezz the "Object 5 Reading all the Notecards" that is in the Class Materials folder.
The script has the same structure than the Script 3 reading poems from Wednesday.
In the linkset data, instead of poems, we have notecards. The key identifies the notecard and the line.
We are doing an improvement from the other script.
We were copying the name of the poem in each key, for each line, and this uses linkset data memory.
Now, instead of using the name of the notecard in the key, we are:
- making a list with the names of the notecards, in the script memory.
- using the index in this list for the key, instead of the name.
Let's look at the changes in the script, starting with the events.
In state_entry we delete the linkset data, to start reading the notecards again.
notecardIndex is the counter of the notecards in the contents, starting with 0.
And we call notecardRead() to start reading the first notecard.
touch_start calls the function to list the notecards and listen calls the function to say the notecard selected by the owner, in the same way than the other script.
dataserver works the same, calling lineRead() in line 91 to write the line in the linkset data.
And at the end of the notecard, lines 99-102, we increase the counter and go to read the next notecard.
Let's go to the functions.
notecardRead(), lines 9-18, is added in this script. It starts reading a notecard.
We use notecardIndex, the counter of notecards to get the name in line 10.
When there aren't more notecards, a notecard with the notecardIndex doesn't exist llGetInventoryName() returns "" and we say a message to the owner with the quantity of notecards read.
If the notecard exists, lines 11-14, we add it to our list notecardNames , and we request its first line.
This function is called the first time from state_entry, and after reading each notecard from dataserver.
lineRead(), lines 20-25, write in the linkset data.
It's easier than in the other script, because we already have the key components in global variables.
We add zeroes to notecardIndex to start with "01", and to notecardLine to start with "001".
Now the function to list and say.
listNotecards(), lines 27-36, is also easier than before, because we already have the names of the notecards in the global list notecardNames.
We loop on the list to say the names.
And we say a notecard in sayNotecard(), lines 38-64.
As before we check is the number of notecard selected by the user exists.
We show our list to the owner starting with 1. In line 48 we decrease the number selected to be the index of the notecard in the list starting with 0.
We get the title of the notecard from the list of name and the keys of the lines, starting with the number of the notecard, in lines 49-50.
We say the title and loop on the keys to read and say the lines.
=== Next week: Info Boards
With this week 8 we have finished the basics of scripting.
We will be using the contents of these 8 weeks often in the rest of the weeks, so we will be studying it once and again, seeing more examples.
You need to learn well these 8 weeks to be good scripters.
From now on the weeks will be focused on one scripting topic, more independent of each other. Some things we will use again in other weeks, some other things will be seen only that week.
The structure of the week will change slightly.
As usual, Monday are classes and Wednesday are questions, practices and sometimes some more contents, related to Monday classes.
But Saturdays will be questions and practices related to the basic concepts, without new contents.
Next week, week 9, we will study Info Boards.
The idea is to have a board, one prim with a texture showing different options.
We will detect the option touched on the board, to give objects, notecards, landmarks, links, group and contact info, etc.
We will use a configuration notecard to tell the script what to do.
Answers to questions
Why do we delete the linkset data every time?
- We delete the linkset data when the script is reset and read the notecards again, to be sure that everything is updated.
- No need to do it in this script, we could check it with the changed event, but it's done this way to keep the script easier.
Where in the script do we reset the numbers, so that the first one isn't 0?
- When we list the notecards, in line 34, we add 1 to the index, to show the list of notecards starting with 1, which is more bio-like.
- It means that the user will say a number that is the index of the notecard plus 1, so we need to decrease the input from the user by 1.
- We decrease it in sayNotecard() in line 48. The parameter notecard is the number that the user has said, coming from the event listen.
What happens when NAK is detected in dataserver?
- To read the first line of the notecard we use the async function to request it, then usually the notecard will be stored in the sim.
- We read the notecard in the event dataserver, in the do...while loop, lines 89-98, using the sync function.
- If a NAK appears, very uncommon but it could happens, we request the same line again, this time with the async function, in line 95.
- Also if there is a NAK, we check it in the while to leave the loop, because we will go on reading when the dataserver is triggered again.
- All happens in the dataserver code, the dataserver is triggered when we request the first line in the notecard and if there is a NAK is triggered again with the line.
- When the notecard finishes it calls notecardRead() in line 101 to read the next notecard.
- In short, the process is:
- readNotecard() - line 0 - dataserver - NAK - dataserver - EOF - readNotecard.
What is the function of llToUpper()?
- llToUpper() changes a string to uppercase.
- llToLower() changes to lowercase.
- In this case we want to say the title of the notecard in uppercase.
- The title is in uppercase when the poem is said, but not when the poem titles are listed.
If we have 5 notecards and the script reads them all, and then we remove some of notecards and put some others, will the script update itself?
- Yes, because the removing or adding of notecards will trigger the event changed and there we reset the script, delete the linkset data, and read all the notecards again, in lines 106-111.
Has the linkset data limits on storage size and key count?
- The linkset data has a limit on storage, 128k, no limit on the quantity of keys, only limited by the storage.
What will happen if we run out of storage?
- If there is no space to write the key and the value, llLinksetDataWrite() does nothing.
Could it happen that when we try to write a long chunk of data to linkset data, it fails, but when we write a shorter piece at a later time, it may write sucessfully?
- Yes, it could happen that one long line is not written, but a next, shorter line, is.
How can we check if the data has been written?
- llLinksetDataWrite() returns a value, the result of the operation:
- integer error = llLinksetDataWrite(...);
- "error" will be the constant LINKSETDATA_OK, which is 0, is all has gone well.
- If it has run out of memory, the error will be LINKSETDATA_EMEMORY.
- If we think that our script can run out of linkset data memory, it's better to use the returned value to check for it.
- The list of errors and its constants is here: https://wiki.secondlife.com/wiki/LlLinksetDataWrite#Caveats
- LINKSETDATA_EMEMORY is returned when there is not enough space to write the key_value pair, both together, if there is no space for both, nothing is written.