State
LSL features
states which
scripts can define in many ways, switching back and forth between them using the
state directive (example:
state default;).
States are defined using the keyword
state (Example:
state foo {...}) with the exception of the "
default" state, which is defined by using the keyword
default on its own.
All scripts must have a default state, which also must be the first state entered when the script starts. If another state is defined before the default state, the compiler will report a
syntax error.
States contain
event handlers that are triggered by the LSL virtual machine. All states must supply at least one event handler--it's not really a state without one. If a state is declared without any event handlers, the
compiler displays a syntax error.
When the interpreter reaches a state change statement, the event containing the state change is immediately ended. If the current state has a
state_exit event, this is executed before the state change takes place. Note: previous versions may have allowed the event containing a state change to complete before the state change takes place, but this is no longer the case.
When a state changes, all pending events are cleared, and many (but not all) events that require setup (via a
function) are defaulted (disabled).
state_entry
The
state_entry event occurs whenever a new state is entered, including program start, and is always the first event handled. No data is passed to this event handler.
It is a common mistake to assume that the
state_entry events is
called when an
object is
rezzed out of
inventory. When an object is added into an inventory, the script's current state is saved, so there will be no
state_entry during the rez. If startup
code is needed every time an object is created, create a
global function and call it from both
state_entry and the
on_rez event.
integer listenHandle; // llListen() handle holder.
// global initialization function.
init()
{
// Remove the old listen callback:
llListenRemove(listenHandle);
// Set up a listen callback for whoever owns this object.
key owner = llGetOwner();
listenHandle = llListen(0, "", owner, "");
}
default
{
state_entry()
{
init();
}
on_rez(integer start_param)
{
init();
}
listen(integer channel, string name, key id, string message)
{
llSay(0, "Hi " + name + "! You own me.");
}
}
state_exit
The
state_exit event occurs whenever the
state command is used to transition to another state. It is handled before the new state's
state_entry event.
Provide a
state_exit if any kind of cleanup work is necessary before leaving the old state.
The
state_exit handler is not called when an object is being deleted; all callbacks, handlers,
sounds, etc, will be cleaned up automatically.
States vs. Global Variables
A state and a set of
global variables can serve the same purpose, and each can be expressed in terms of the other. In general, using states over global variables allows immediate script state assumption without making comparisons--and the fewer comparisons a script makes, the more regular code statements it can run.
Example 1: States
default { // we don't need to define this as a state, because it's the default name.
state_entry(){ // state_entry runs first upon entering this state.
llSetColor(<0,0,0>, ALL_SIDES); // set object colour black.
}
touch_start(integer touched){ // touch_start waits for someone to click the object.
state on; // change to state on.
}
}
state on { // this isn't the default state, so we need to define it by saying "state on", rather than just "on".
state_entry(){ // state_entry runs first upon entering this state.
llSetColor(<1,1,1>, ALL_SIDES); // set object colour white.
}
touch_start(integer touched){ // touch_start waits for someone to click the object.
state default; // change to state default.
}
}
In this example, we see what at first glance looks pretty complicated, but it's actually fairly straightforward. The
default state runs first, with
state_entry being the first event run within that state. So the first thing the script does is what's inside
state_entry -- in this case, it uses
llSetColor to set the object's colour black. The script then waits for input from the
touch_start event.
When a
user clicks on the object,
touch_start is triggered, and the contents are run. In this case, it switches to the state
on. From that point, the script does exactly what it did in
default -- it runs what's in
state_entry (which sets the colour to White) and then waits for input from
touch_start.
Example 2: Global Variable
integer on = FALSE; // variables declared before a state mean they're global variables;
// they will be recognized by all functions within the script.
default {
touch_start(integer touched) {
if (on == TRUE) { // check to see if the variable "on" is TRUE.
llSetColor(<0,0,0>, ALL_SIDES); // if it IS, set object colour black.
on = FALSE; // then set "on" to be FALSE.
}
else { // we can do "else" here, because whether or not "on" is TRUE is a yes/no question. It either is or it's not.
llSetColor(<1,1,1>, ALL_SIDES); // set object colour white.
on = TRUE; // then set "on" to be TRUE.
}
}
}
In
this example, we only have a single state. Replacing the second one is a global variable, an
integer named
on, which starts with a
value of
FALSE. Upon running the script for the first time, nothing will happen -- it will merely wait for someone to click on it. When clicked, the
touch_start event will run, and an
IF/ELSE statement will check the value of
on.
If
on is found to have a value of
TRUE, the object's
color is set to black, and
on's value is changed to
FALSE. Otherwise, the inverse happens: the object color becomes white, and
on becomes
TRUE.
Conclusion:
Both these scripts perform the same basic function, but one will run more efficiently than the other: Example 1, using states, because it doesn't have to ever
check anything. Nothing needs to be compared, and no variables need to have their values set. By using states rather than a variable, the script's logic path is linear--it just does one thing after another. In Example 2, the logic path branches, as the script checks and
resets the values of its variable.
Not all scripts can necessarily be improved by using states rather than global variables and, frequently, the inverse is also true. Both states and global variables have their place, depending on the situation. As a rule, the less complex logic your script has, the faster it will run, and the better (less
lag) it will be for the sim. If you're doing something like the above example, using states is usually going to be simpler, as long as you have a good grasp of the order in which the different events are triggered. When writing a script, if you're unsure, look up the respective entries to see when a given event will be triggered.
Q: Can I switch states within a user-defined function?
A: For all intents and purposes, no. There is a way to do it, though it is officially unsupported and may be broken at any time in the future.
Q: Can a state call itself (eg. calling state default; while in the default state) to "restart" that state?
A: While it won't give a script error, it effectively does nothing. Calling a state from within itself does not cause a state change, and will not trigger state_entry.
Note: You can create a second state that will send the script back to the state it was called from. For example, if you wish to restart the default state, create another state called restart_default which upon the state_entry it would call the default state.
While this is a little clunky, it will allow you to restart the default state. - BurnmanBedlam
Events |
Global Variables