Introduction to States

The Linden Scripting Language, LSL, has two aspects that are a bit unlike other languages you may know, events, and states. We’ve had a brief Introduction to Events: now let’s talk about states.

One of the essential aspects of any programming or scripting language is the ability to make a decision. This is usually provided by an if/else statement, and LSL is no exception. But LSL also offers a language facility called state.

We people, if in fact we are people, have “states of mind”. We are happy, sad, hungry, sleepy. Depending on our state of mind, we may respond differently to the same situation. Presented with food, if we are hungry, we eat it. If we are not hungry, we eat it anyway. Wait, maybe that’s not a good example.

OK. If we are not sleepy, and we have a good book at hand, we may read it. If we are sleepy, we are more likely to go to bed, or to fall asleep in our chair.

It’s the same with programs. A script often has what we might think of as states of mind. Let’s consider a simple example. Suppose we want to write a script that moves a prim back and forth between two positions. And suppose we want that oscillation to start only when we click the prim, and to stop when we click it again.

We can be pretty sure that the script will include a timer event, and on every tick, it will move the prim. Since there are two places the prim might go, we might handle the choice with an if statement.  Our initial script may look like this:

vector startPosition;
vector otherPosition;

default {
    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + <1,1,0>;
        llSetTimerEvent(0.5);
    }

    timer() {
        if ( llGetPos() == startPosition ) {
            llSetPos(otherPosition);
        } else {
            llSetPos(startPosition);
        }
    }
}

Now this script could be said to have some “state” already. The variable startPosition remembers where the prim was at the beginning. When the timer hits, if the prim is not at the startPosition, it gets moved there. Otherwise, it gets moved to the otherPosition.

The prim’s position is part of its “state”. We access that state using llGetPos(), which returns the prim’s position. We compare that position to startPosition and change the prim’s state accordingly. The variable startPosition can be thought of as recording the prim’s original state.

The prim has another aspect of state that we want to put in our program. That’s whether or not the prim is moving. We want to control that with touch. As is my fashion, let’s begin with tiny steps. The prim starts out moving, and we’ll leave that be. We’ll begin by making it stop when we touch it. To do that, we’ll ad a touch_start event, and stop the timer in there. That will stop the prim. To be extra nice, we’ll move the prim back to its starting position as well. Here goes:

vector startPosition;
vector otherPosition;

default {
    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + ;
        llSetTimerEvent(0.5);
    }

    timer() {
        if ( llGetPos() == startPosition ) {
            llSetPos(otherPosition);
        } else {
            llSetPos(startPosition);
        }
    }

    touch_start(integer ignored) {
        llSetTimerEvent(0.0);
        llSetPos(startPosition);
    }
}

Now our prim really has two states. It starts out moving, and when we touch it, it stops moving. So this script embodies two states of the prim, moving and not moving, but does not show those states clearly in the code yet. Let’s do that, using an integer variable moving, and extend the script to switch between moving and not moving:

vector startPosition;
vector otherPosition;
integer moving;

default {
    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + ;
        moving = TRUE;
        llSetTimerEvent(0.5);
    }

    timer() {
        if ( llGetPos() == startPosition ) {
            llSetPos(otherPosition);
        } else {
            llSetPos(startPosition);
        }
    }

    touch_start(integer ignored) {
        if ( moving ) {
            moving = FALSE;
            llSetTimerEvent(0.0);
            llSetPos(startPosition);
        } else {
            moving = TRUE;
            llSetTimerEvent(0.5);
        }
    }
}

Now we have represented the prim’s state, moving or not moving, with a “state variable” in the script, representing whether we are moving. And we have our script working: it moves back and forth and we can turn it on and off. However, it doesn’t quite do what we’d like because it starts out moving. We fix that readily, by not starting the timer in state_entry, and by setting the moving variable to FALSE:

vector startPosition;
vector otherPosition;
integer moving;

default {
    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + ;
        moving = FALSE;
    }

    timer() {
        if ( llGetPos() == startPosition ) {
            llSetPos(otherPosition);
        } else {
            llSetPos(startPosition);
        }
    }

    touch_start(integer ignored) {
        if ( moving ) {
            moving = FALSE;
            llSetTimerEvent(0.0);
            llSetPos(startPosition);
        } else {
            moving = TRUE;
            llSetTimerEvent(0.5);
        }
    }
}

OK, this is doing exactly as we want. It’s time to improve the code. You’ll find, as you read my blog here, that I like to pause whenever the program reaches a working point, and remove duplication and make other changes that improve the code according to my standards.

One of favorite notions is one of expressing intention in the code, not just the algorithm. This makes it easier for me, and other readers, to figure out what’s what. Some people recommend comments, and I will use them as well. I was taught, however, to use comments only in places where the code can’t be made so expressive as not to need them. So let’s see what we see.

Inside the if statement in the touch_start, there are two little blocks. What do they do? Well, the first block stops the motion, and the second block starts the motion. Let’s express that:

vector startPosition;
vector otherPosition;
integer moving;

startMotion() {
    moving = TRUE;
    llSetTimerEvent(0.5);
}

stopMotion() {
    moving = FALSE;
    llSetTimerEvent(0.0);
    llSetPos(startPosition);
}

default {
    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + ;
        moving = FALSE;
    }

    timer() {
        if ( llGetPos() == startPosition ) {
            llSetPos(otherPosition);
        } else {
            llSetPos(startPosition);
        }
    }

    touch_start(integer ignored) {
        if ( moving ) {
            stopMotion();
        } else {
            startMotion();
        }
    }
}

Cool. I think that is far more expressive, don’t you? When we touch, if moving we stop motion, otherwise we start motion. Nice. What else do we see? Well, the statement

span style="font-size:large;">moving = FALSE

occurs twice, once in the function and once in state entry. We can remove that duplication by calling stopMotion in state_entry.

Note that this does do more things, stopping the clock, and setting the position to where it already is. When we make this kind of a change we need to be careful, and in this case we are sure it’ll work just fine.

You might also be concerned that it would be “more efficient” not to do those two additional statements. Except in cases of very high performance, I prefer to focus on clear code, not on absolutely tight code. Others may not agree. They are wrong. :) Here’s the code again, just that one bit:


    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + ;
        stopMotion();
    }

What else do we see right now? Well, now the touch_start has an if statement in it:


    touch_start(integer ignored) {
        if ( moving ) {
            stopMotion();
        } else {
            startMotion();
        }
    }

This certainly has to be somewhere, but we might like to express what it does. What does that if statement do? It switches, or toggles, between moving and not moving. We could do this:

vector startPosition;
vector otherPosition;
integer moving;

startMotion() {
    moving = TRUE;
    llSetTimerEvent(0.5);
}

stopMotion() {
    moving = FALSE;
    llSetTimerEvent(0.0);
    llSetPos(startPosition);
}

toggleMotion() {
    if ( moving ) {
        stopMotion();
    } else {
        startMotion();
    }
}

default {
    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + ;
        stopMotion();
    }

    timer() {
        if ( llGetPos() == startPosition ) {
            llSetPos(otherPosition);
        } else {
            llSetPos(startPosition);
        }
    }

    touch_start(integer ignored) {
        toggleMotion();
    }
}

OK, that’s about as clean as I care to make it, and perhaps more clean than you would choose. Mark my words, children, don’t play in the street, and keep your code clean. It doesn’t matter in a script this size, but in a big one it makes all the difference between understanding and confusion. I prefer understanding.

Our purpose here, though, is to describe the LSL state facility. We have written a program that represents state using variables and functions. Now lets use the state capability itself.

In LSL, the script always has at least one state, called default. Up until now, maybe you’ve just learned to put your code inside the default{} and paid no more attention. I know I didn’t, for a while. But there can be more than one state, there can be many. Each state is defined using the word “state”, and you can also move from one state to another using the word “state”. (Sorry about that, I’m just documenting it, not inventing it.) We’ll convert our program to use states, and I plan to do it in one go. We’ll have the default state initialize things, and have two new states, called “moving” and “not moving”. When we are in not moving and we are touched, we’ll go to state moving. When we are in moving, we’ll go back to not moving. It looks like this:

vector startPosition;
vector otherPosition;

startMotion() {
    llSetTimerEvent(0.5);
}

stopMotion() {
    llSetTimerEvent(0.0);
    llSetPos(startPosition);
}

default {
    state_entry() {
        startPosition = llGetPos();
        otherPosition = startPosition + ;
        state not_moving;
    }
}

state not_moving {
    state_entry() {
        stopMotion();
    }

    touch_start(integer ignored) {
        state moving;
    }
}

state moving {
    state_entry() {
        startMotion();
    }

    touch_start(integer ignored) {
        state not_moving;
    }

    timer() {
        if ( llGetPos() == startPosition ) {
            llSetPos(otherPosition);
        } else {
            llSetPos(startPosition);
        }
    }
}

Got it? Start in default, save the starting data, go to state not_moving, stop motion if any is going on. Wait for a click, go to state moving. In state moving, start motion, and wait for a click, go back to not_moving.

So we have started with a prim that had state, whether it was moving or not, with no direct representation of that in our script. We added a variable and used that to decide whether to start the motion or stop it. We removed the variable and added two additional states to provide the same function.

If you’re new to these ideas, I’d urge you to experiment with some simple scripts like this one, to get a sense of how to use the LSL state capability. One bit of advice: states should be used with a bit of caution, because once you’ve made your script use them, it is difficult to change your mind and use them differently. As a result, I usually try to hold back from using explicit LSL states until the script is fairly mature.

Now I reached my current way of doing it by trying lots of different ways and paying attention to where it felt good, and where it didn’t. I suggest you do the same. Don’t be afraid to experiment … just keep your script under control.

Happy scripting!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: