Skip to content

Instantly share code, notes, and snippets.

@Fawrsk
Last active March 8, 2021 18:41
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fawrsk/1d00387aa0d0df314e6787da002eb73a to your computer and use it in GitHub Desktop.
Save Fawrsk/1d00387aa0d0df314e6787da002eb73a to your computer and use it in GitHub Desktop.
Tips and tricks for writing Linden Scripting Language (LSL) scripts.

LSL Tips and Tricks

Contributors

  • Fawresin

Disclaimer: The used coding style and naming conventions are for the reader's clarity and do not reflect the author's opinions.

Table of Contents

  1. Break and Continue Within Loops
    1. Solution 1
    2. Solution 2
  2. Multiple Timers
    1. Solution 1
    2. Solution 2
  3. Chained Timers
    1. Solution 1
  4. Verbosity
  5. Simplifying For Loops
    1. Solution 1

The Linden Scripting Language (or just LSL), is a somewhat primitive language you are required to mess with if you want stuff to work within Second Life. Sometimes you'll have to get creative to accomplish certain things, so here is where I'll list some tips I've picked up over the years that may be useful to other scripters.

Also feel free to fork this document and add your own!

1 - Break and Continue Within Loops

At some point, you may have come across a situation where you wanted to prematurely exit a loop, or skip to the next iteration of one. In other programming languages, this can be accomplished using the break and continue statements. Unfortunately LSL does not have these statements, so you have to resort to other methods:

i) Make the iteration condition be false.

This method is simple, but has the drawback of requiring everything be wrapped inside of an if/else statement. It is also harder to deal with inside of nested loops.

list words = ["one", "two", "three", "four"];
integer total = llGetListLength(words);
integer i;
for (i=0; i<total; ++i)
{
    string word = llList2String(words, i);
    if (word == "three")
    {
        // Cause the loop to exit the next time it checks the condition.
        i = total;
    }
    else
    {
        // Ghetto way to skip the second element.
        if (word != "two")
        {
            // Regular loop code.
        }
    }
}

ii) Using jump statements, aka gotos.

This method can be used to break out of nested loops, and can also be used for continue statements. You might've heard that using gotos is bad. While that is true for the most part, there are times where it can be convenient. Convenience is a nice thing to have in a language like LSL. To do this, you place labels at the very end of the loop body, and right after it.

list words = ["one", "two", "three", "four"];
integer total = llGetListLength(words);
integer i;
for (i=0; i<total; ++i)
{
    string word = llList2String(words, i);
    if (word == "three")
    {
        // Exit the loop.
        jump break;
    }
    else if (word == "two")
    {
        // Continue to the next iteration.
        jump continue;
    }

    // Regular loop code.

    @continue;
}
@break;

2 - Multiple Timers

Occasionally you'll need to have separate timers, but LSL only lets you have one.

i) This method involves setting the interval to a low value which covers all of the timer intervals, and a variable for each "timer" that stores the script time at which they should execute.

float INTERVAL_1 = 2.0;
float INTERVAL_2 = 5.0;

float timer_1 = INTERVAL_1;
float timer_2 = INTERVAL_2;

default
{
    state_entry()
    {
        llSetTimerEvent(1.0);
    }

    timer()
    {
        float time = llGetTime();
        if (time >= timer_1)
        {
            llOwnerSay((string)time + " - Timer 1");
            timer_1 = time + INTERVAL_1;
        }

        if (time >= timer_2)
        {
            llOwnerSay((string)time + " - Timer 2");
            timer_2 = time + INTERVAL_2;
        }
    }
}

ii) Another method is to use llSensorRepeat on a sensor that will never detect anything, and use the no_sensor event handler as a secondary timer. This method is the most easy to use, but sacrifices a sensor.

float INTERVAL_1 = 2.0;
float INTERVAL_2 = 5.0;

default
{
    state_entry()
    {
        llSetTimerEvent(INTERVAL_1);
        llSensorRepeat("invalid", llGenerateKey(), AGENT, 0.01, 0.01, INTERVAL_2);
    }

    timer()
    {
        llOwnerSay("Timer 1");
    }

    no_sensor()
    {
        llOwnerSay("Timer 2");
    }
}

3 - Chained Timers

Maybe you've come across a situation where you need to have separate timers that fire off one after another.

i) This method uses a variable that stores the "ID" of the timer, aka an integer that you increase the number of inside of each timer branch.

float TIMER_1 = 2.0;
float TIMER_2 = 5.0;
float TIMER_3 = 10.0;

integer timerID;

resetTimers()
{
    timerID = 0;
    llResetTime();
}

// Returns if the current timer ID is ready to go off.
integer isTimer(integer id)
{
    float time = llGetTime();
    integer is_timer =
       timerID == id && (
           // Add more timers here.
         (time >= TIMER_1 && id == 0) ||
         (time >= TIMER_2 && id == 1) ||
         (time >= TIMER_3 && id == 2)
       );

    if (is_timer)
    {
        // This saves putting it inside of each branch.
        ++timerID;
    }

    return is_timer;
}

default
{
    state_entry()
    {
        llSetTimerEvent(1.0);
        resetTimers();
    }

    timer()
    {
        float time = llGetTime();
        if (isTimer(0))
        {
            llOwnerSay((string)time + " - Timer 1");
        }
        else if (isTimer(1))
        {
            llOwnerSay((string)time + " - Timer 2");
        }
        else if (isTimer(2))
        {
            llOwnerSay((string)time + " - Timer 3");
            resetTimers();
        }
    }
}

4 - Verbosity

LSL's verbosity can be a little tiresome at times. This is probably common sense, but try to break things up into functions for anything that you find yourself repeating. Duplicate code sucks to read and write. You can also wrap LL's long function names to something simpler and maybe with more conveyance than the original.

Funnily enough, I accidentally came across a reserved print function while writing this.

// Print something to the owner.
println(string message)
{
    llOwnerSay(message);
}

// Sets a link's primitive params using the sleepless function.
set_prim_params(integer link, list rules)
{
    llSetLinkPrimitiveParamsFast(link, rules);
}

5 - Simplifying For Loops

i) This method is more of a trick to alternatively writing for loops. It's a good way to iterate over a list without caring about order (unless it's backwards). It uses a while loop, and a single integer variable that decrements on each iteration. Since anything not zero is equal to TRUE, and zero itself is equal to FALSE, it will count down until the integer variable hits zero. However, you must use postfix decrementation or it will be short one iteration.

integer i = 100;
while (i--)
{
    llOwnerSay((string)i);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment