a tale of Wyverns, Dragons and Ponies

It’s an event…

November 19th, 2008 Posted in Purple Wyvern Designs

Sometimes weird bugs creep into scripts especially those that have morphed and grown over time.  I was having a problem with settings not getting stored by the object and getting reset to default or stuck on an old value.  I was wondering if I had found a weird SL bug, but it turned out I had been unintentionally using a workaround that was no longer working in the latest server version and I had a basic script mistake that I was working around with some over complicated functionality.

My problems stemmed from the fact my scripts often contain the following statement:

default {
on_rez(integer start_param){
llResetScript()
}

I primarily do this in any script where I use llGetOwner() in order to ensure that any listeners or other functions depending on the users key are set up correctly.  However, resetting the script means that it sets any variables used back to default values.  My work around for this was to store the variables in the description field of a child primitive, a technique known as primitive parameter overloading.

If my objects were just rezzed on the ground, I would of been OK at that point.  But they are not, they are worn attachments.  Attachments don’t tend to easily let you edit there description fields when worn.  If the description is blank, it seems to let you write it once or twice, but not always.  Attachments will revert any object name changes on being un-worn, it looks like the description field gets reverted as well.

However, after a little advice from a friend who mentioned that I was probably doing something fundamentally wrong and I that shouldn’t be using llResetScript() in the on_rez event if I wanted to persist data, I decided to go back and revisit events and how they are triggered.

So back to school for me.

First, lets do some testing.  Lets consider the following bit of test code

integer myValue = 0;

default {
on_rez(integer start_param) {
llInstantMessage(llGetOwner(),"on_rez: "+(string)start_param);
llInstantMessage(llGetOwner(),"myValue: "+(string)myValue);
}
state_entry() {
llInstantMessage(llGetOwner(),"state_entry");
llInstantMessage(llGetOwner(),"myValue: "+(string)myValue);
}

state_exit() {
llInstantMessage(llGetOwner(),"state_exit");
llInstantMessage(llGetOwner(),"myValue: "+(string)myValue);
}

attach(key attached) {
llInstantMessage(llGetOwner(),"attach: "+(string)attached);
llInstantMessage(llGetOwner(),"myValue: "+(string)myValue);
}

touch_start(integer total_number) {
llInstantMessage(llGetOwner(),"touch_start: "+(string)(++myValue));
}
}

I’ve used llInstantMessage() here because everyone hates spammy debug shouting at them.  I could of used llOwnerSay() but I considered that I might want to test across sim borders (llOwnerSay seems to cover entire regions).  The only downside to llInstantMessage is that it sleeps the prim for a couple of seconds when used.

So drop the script into the test prim, what do you get?

Test Object: state_entry
Test Object: myValue: 0

State entry is called when you script is first put into a prim, because this is the first time it enters the default state. OK, take it into inventory and rez it back out again, note that state_exit never gets called

Test Object: on_rez: 0
Test Object: myValue: 0

First thing to note here, state_entry is not called! This is because the script never left the (current) default state. The on_rez event is always called when the object is rezzed (you might like to try walking away from the prim until it is out if sight and then walking back towards it and see what happens).

Next, touch it a couple of times, take the prim back to inventory and then wear it

Test Object: on_rez: 0
Test Object: myValue: 2
Test Object: attach: 1b61897c-d2ea-4314-bca4-d5d9307c1ba4
Test Object: myValue: 2

Note, the on_rez is called as well as the attach event, taking the object back off gives you

Test Object: attach: 00000000-0000-0000-0000-000000000000
Test Object: myValue: 2

Which is correct.  The null key indicates that the object is being detached.

If I reintroduce llResetScript() into the on_rez, then I start to run into problems.  When I attach the object, because on_rez is being called myValue is reset to declared default.  So instead of the messages above, I get the following:

Test Object: on_rez: 0
Test Object: state_entry: 0
Test Object: myValue: 0
Test Object: attach: 1b61897c-d2ea-4314-bca4-d5d9307c1ba4
Test Object: myValue: 0

Ouch, variables reset, and I get both on_rez and state_entry.  But I can’t simply remove the llResetScript() because that would mean that the object would listen only to me and not the new owner when they purchase it. 

Fortunately a friend had the solution to how it should be done, the following code snippet was suggested to me:

changed(integer change) {
if (change & CHANGED_OWNER) {
llResetScript();
}
}

This makes the items reset when the owner is changed – excellent! This is probably the code that I should also have in all my box unpacking scripts as well.

However, what happens if I don’t want it to reset because it’s a limited use item?  As long as the item isn’t transferable, then this is still OK.  However, if the item is transferable then using this code would mean the person would only have to transfer the item to refresh the usage – not good!

The answer, well that is in the LSLWiki, use a global initiation function in both the state_entry and on_rez events and that should catch all instances:

init() {// do lots of initiation here }
default {
on_rez(integer start_param) {init();}
state_entry() {init();}

This is of course really basic stuff.  But sometimes its the basic stuff that trips you up!

This is also a lesson about relying too heavily on debuggers.  I have of course tested my scripts in LSLEditor first before deploying them.  However, LSLEditors debugger lets you fire off the individual events.  But if you are assuming that events are triggered in situations where they are not – then it can’t correct that mistake.

You must be logged in to post a comment.