Jump to content

Best Scripter Tips and Shortcuts


Lexie Linden
 Share

You are about to reply to a thread that has been inactive for 389 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

On 6/10/2018 at 5:56 PM, Madelaine McMasters said:

...

It's a shame that LSL doesn't have conditional interpretation, allowing unwanted debug code to be ignored. "if(iDebug)" doesn't take much time to execute, but it does take time and the debug code takes script memory. In the world where I worked, there were "preprocessor" variables that controlled how the compiler handled various statements. If I'd made "iDebug" a preprocessor variable rather than a program variable and set it to FALSE, the compiler would ignore the block and no code would be generated for it at all. If "iDebug" were TRUE then the code would be generated and no test for its value was necessary. It's no longer a matter of deciding whether to execute debug statements as the program runs, they're either in the program or they aren't.

...

 

Half of a year delayed reply, but what the heck! Not sure since when, but Firestorm does come with a pre-processor option, which works very similarly to the C/C++ pre-processor. ☺♥

Link to comment
Share on other sites

  • 1 month later...
  • 4 months later...

some ideas on Listens ...

 

you could use the owner's id & the item/hud/rezzer's id to create a unique channel.

       ownerChan  = 0x80000000 | (integer)("0x"+(string)llGetOwner() );    
        itemChan   = 0x80000000 | (integer)("0x"+(string)llGetKey() );    
        chan = ownerChan + itemChan;
        chan_handle = llListen(chan,"","","");   

this might be good for a rezzer, which has items that need to listen to the rezzing obj.

 

 you could also just use a name, if you have a specific item you need to talk to.

string itemName     = "receiver_one";

string msg= "|color|<0,1,0>";

 llRegionSay(ownerChan, itemName + msg );

 

and in the receiving obj, check for the name...

 myName = llGetObjectName();   

 list sent = llParseString2List(message,["|"],[""]);              
               if(llList2String( sent,0) != myName) return;

 string command = llList2String( sent,1);
 string command_text = llList2String( sent,2); // change to vector, rot etc if needed

 

just looking for different ways to do things ....

Edited by Xiija
Link to comment
Share on other sites

34 minutes ago, Xiija said:

you could use the owner's id & the ite/hud/rezzer's id to create a unique channel.

       ownerChan  = 0x80000000 | (integer)("0x"+(string)llGetOwner() );    
        itemChan   = 0x80000000 | (integer)("0x"+(string)llGetKey() );    
        chan = ownerChan + itemChan;
        chan_handle = llListen(chan,"","","");   

this might be good for a rezzer, which has items that need to listen to the rezzing obj.

Minor nit-pick, but you don't have to explicitly typecast keys to string before adding another string to it.

And one thing regarding rezzers is that you can use the object_rez event to send messages to the newly rezzed objects. They're always guaranteed to exist and you'll have their key. This way, you can send messages even to multiple objects rezzed in quick succession (especially if they are rezzed by other scripts in the same object). Because events have a queue, you're guaranteed to send a message to each rezzed object.

Though, for this method to work absolutely, the objects-being-rezzed must already have an open channel before they are rezzed. (llListen in state_entry, then pick up and put into rezzer. New copies of that object will never miss chat messages.) I can personally vouch for this simple trick because I've  been using it for quite a while now.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

1 hour ago, Wulfie Reanimator said:

And one thing regarding rezzers is that you can use the object_rez event to send messages to the newly rezzed objects. They're always guaranteed to exist and you'll have their key. This way, you can send messages even to multiple objects rezzed in quick succession (especially if they are rezzed by other scripts in the same object). Because events have a queue, you're guaranteed to send a message to each rezzed object.

Though, for this method to work absolutely, the objects-being-rezzed must already have an open channel before they are rezzed. (llListen in state_entry, then pick up and put into rezzer. New copies of that object will never miss chat messages.) I can personally vouch for this simple trick because I've  been using it for quite a while now.

That's a classic method, and it usually works well.  I've used it myself for years.  As @Oz Linden noted in this forum a year ago, though, recent changes in the way that event timing is managed can sometimes make this method fail.  Specifically, even though a channel should be opened in the new object on rez, the object may be slow to rez or its scripts may be slow to execute.  If you send information too soon, the new object will never hear it. My routine way to beat the problem had always been to put a short llSleep -- 0.3 - 0.5 seconds -- in the rezzer's object_rez event before sending vital information. As Oz reminded us, though, that small sleep may still not be sufficient in a laggy region.  The safer solution is to use handshaking:

1. Pass the newly-rezzed object the channel as a startup parameter in on_rez.

2. Open the channel with llListen.

3. Have the new object send the rezzer a "Here I am" message to announce that it is ready to listen.

4. Have the rezzer transmit the information only after it receives the "Here I am" message.

Most of the time, handshaking isn't really necessary, but you can't count on that always being true.  

Edited by Rolig Loon
typos. as always.
  • Like 3
Link to comment
Share on other sites

1 hour ago, Rolig Loon said:

As @Oz Linden noted in this forum a year ago, though, recent changes in the way that event timing is managed can sometimes make this method fail.  Specifically, even though a channel should be opened in the new object on rez, the object may be slow to rez or its scripts may be slow to execute.  If you send information too soon, the new object will never hear it.

This is not the case. I'm aware of the changes (and the thread where they provided a (fairly poor) example on the correct method, similar to what you described), but it does not affect this trick.

The way the chat system itself works is kind of similar to the event queue. The script's listener will poll for new messages and is guaranteed to hear "unheard messages" that have been sent before the script actually started running. (As long as the object was alive, and unless the script's event queue is full and the message is received but the event is discarded.) Most recently, I used it for a gun intended for active SL combat. (It needed to have a visual "bullet trail" for its raycast mode, and I send the intended position to the visual prim with llRegionSayTo.) Lag is guaranteed to exist, but it never fails even when it gets so bad that avatars stop moving.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

7 hours ago, Wulfie Reanimator said:

The way the chat system itself works is kind of similar to the event queue. The script's listener will poll for new messages and is guaranteed to hear "unheard messages" that have been sent before the script actually started running.

Is that really true? If that were the case, we wouldn't be having problems with newly rezzed objects missing messages from the object that rezzed them. See this article.

I do the full handshake thing with my escalators. When they're installed or moved, the frame rezzes the moving step part. That's keyframe-animated, so it has to be a root prim. You have to go through this whole drill on anything that rezzes its moving parts. Having to do a full handshake is a pain. I wish you could send a list, instead of just an integer, as param in llRezObject. Then you could pass whatever info the rezzed object needs to get started.

Link to comment
Share on other sites

7 hours ago, animats said:

Is that really true? If that were the case, we wouldn't be having problems with newly rezzed objects missing messages from the object that rezzed them. See this article.

I do the full handshake thing with my escalators. When they're installed or moved, the frame rezzes the moving step part. That's keyframe-animated, so it has to be a root prim. You have to go through this whole drill on anything that rezzes its moving parts. Having to do a full handshake is a pain. I wish you could send a list, instead of just an integer, as param in llRezObject. Then you could pass whatever info the rezzed object needs to get started.

Yup, that's the article I've read before (and commented on the respective thread on this forum).

This wiki page explains it, and I could be wrong but I'm mainly convinced because I don't believe I'm so lucky that dropped messages happen so rarely in the unavoidably laggy conditions the gun is intended for that I don't notice and a whole group of people haven't mentioned it even once. Sometimes there's a delay of a couple seconds before the rezzed object acts on the message, but my rezzer sends the message (once) as soon as object_rez triggers, so it's definitely not getting lost.

The thing is, if your rezzed object is supposed to call llListen during on_rez, it won't hear messages during the "object rezzed but scripts haven't started" period, because the channel isn't open.

Quote

The parent object receives an object_rez() event when the new object has been created, but it is never safe to assume that scripts in the new object have had a chance to run when the object_rez event is delivered. This means that the new object may not have initialized its listen() event or called llAllowInventoryDrop, so any attempt to send it messages or inventory could fail.

See, the article specifically assumes that the rezzed object initializes stuff after it has been rezzed. I don't do that. I initialize the listen before the object is taken into inventory. The same should be possible with llAllowInventoryDrop, I haven't tried.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

31 minutes ago, animats said:

So "listening" is a prim property that survives storage in inventory. Interesting, and unexpected.

Something like that. There's two parts to it, the first is that objects rezzed by scripts don't have their scripts reset. Try putting this:

integer global;

default
{
    touch_start(integer n)
    {
        ++global;
        llOwnerSay((string)global);
    }
}

Into this, after you have touched it a couple times to change the global:

default
{
    touch_start(integer n)
    {
        llRezObject("Object", llGetPos() + <0,0,0.5>, ZERO_VECTOR, ZERO_ROTATION, 0);
    }
}

You'll notice that the counter continues from where it was when you picked up the first object. Similarly, if the first object called llListen to open a channel, it would remain in effect for that script even after being rezzed as a new copy.

The second part gets a lot more speculative. I don't think it's a "prim property" in the sense that there's any data stored in the object's attributes (which are sent to the viewer in full), more so that the state of the script contains a listener, and the moment the object is created, so is the script and the sim immediately becomes aware of its listener (because recovering the state of the script would inherently re-register the listener as well, and marking the beginning of the "chat history" that should eventually be sent to the script), long before the script becomes active.

Edited by Wulfie Reanimator
  • Like 2
  • Thanks 2
Link to comment
Share on other sites

You've made some good observations, @Wulfie Reanimator, and I think they account for much of your success with this method so far.  I am not personally convinced that I can count on a listener being active on rez, so we differ there.  Lag and odd server behavior have shaken my confidence over the years. From a very practical perspective, though, the best reason I can think of for preferring to handshake is that it reduces the number of ways that I can screw up absent-mindedly. 

Like most scripters, I often direct scripts to reset on rez, to be sure that they (1) know who the current owner is, (2) have re-initialized key variables, (3) have as much free memory as they need, (4) are starting in state default, and a host of other things, all of which add up to starting on rez with a clean slate.  I don't always do that by any means -- I can achieve most of those outcomes in other ways, after all.  The more complex a script is, though, the more likely I am to reset it on rez as a safety precaution, just in case the script is remembering prior conditions that I would rather have it forget.  I suspect that many scripters do the same (although not necessarily thinking through the laundry list of things I just listed).

It is also my frequent practice to communicate with newly-rezzed objects on unique channels.  I can do that either by generating a random channel number to send as a startup parameter on rez or by creating a channel number from the UUID of the new object.  That way, I can rez several objects in a row and be confident that they won't have crosstalk problems with each other or with the rezzer.  I wouldn't bother to do this with very simple objects but again, the more complicated the setup, the more important it is to be sure that rezzed objects have unique ways to communicate with the rezzer.

I can think of other reasons why I very frequently start with rezzed objects that don't -- and shouldn't -- have open channels.  Whether they do or not, it's safer for me to assume that there's no open channel until I've completed a handshake.  That way, if I have not reset the script -- whether on purpose or because I forgot to -- I have at least reduced the chances that there's no open channel to receive valuable information from the rezzer.  By very loose analogy with RL, this is similar to what I do when my son is about to drive his family home after a visit.  As he backs out the driveway, I say, "Call me when you get home."  I'm pretty sure that he'll get there in one piece, and he does have a cell phone anyway.  Still, when he makes a simple call as he arrives, we have rebooted the link between us and we can both relax and get on with scheduled activity.  

  • Like 2
Link to comment
Share on other sites

11 hours ago, Wulfie Reanimator said:

The second part gets a lot more speculative. I don't think it's a "prim property" in the sense that there's any data stored in the object's attributes (which are sent to the viewer in full), more so that the state of the script contains a listener, and the moment the object is created, so is the script and the sim immediately becomes aware of its listener (because recovering the state of the script would inherently re-register the listener as well, and marking the beginning of the "chat history" that should eventually be sent to the script), long before the script becomes active.

this makes a lot of sense to me

the state of a running script is also saved when taken into inventory, and when we drag copy an existing object with a running script

given that such an object when rezzed contains a running script with saved state then I think your findings can be substantiated as being by design and not an anomaly

  • Thanks 1
  • Confused 1
Link to comment
Share on other sites

Love, when we copy a running script (and don't reset it) then the state_entry event of the default state doesn't fire, so the listener by design has to be saved with the state of the script as are any global variables values and the current timer value

as Wulfie says messages are queued to a global list. It appears that when a running script with an open listener is instantiated then it is given/processes the message queue in its entirety, not just messages queued after its instantiation

  • Like 1
Link to comment
Share on other sites

15 minutes ago, Mollymews said:

Love, when we copy a running script (and don't reset it) then the state_entry event of the default state doesn't fire, so the listener by design has to be saved with the state of the script as are any global variables values and the current timer value

as Wulfie says messages are queued to a global list. It appears that when a running script with an open listener is instantiated then it is given/processes the message queue in its entirety, not just messages queued after its instantiation

I see Wulfie's explanation of this. I read it after your post. I found your post confusing because, it implied there were benefits to this arrangement besides the specific cases discussed earlier - but I did not see anything in your previous post that helped explain why this was useful. Wulfie's post did a good job of that (you repeated some of the info above).

Edited by Love Zhaoying
Link to comment
Share on other sites

1 minute ago, Love Zhaoying said:

I did not see anything in your previous post that helped explain why this was useful

it was more that it made sense to me that the behaviour shown by Wulfie is by design and is not an exploitable anomaly. And if so then why would this be

  • Like 1
Link to comment
Share on other sites

25 minutes ago, Mollymews said:

the behaviour shown by Wulfie is by design

I see many, many uses for it. But, like with a lot of modern products - all you'd have to do is "reset all scripts in object" (like when people break Mesh HUDs, etc.) to lose whatever "important" info was saved when the script was suspended and copied to inventory. That's pretty fragile.

Link to comment
Share on other sites

5 minutes ago, Love Zhaoying said:

I see many, many uses for it. But, like with a lot of modern products - all you'd have to do is "reset all scripts in object" (like when people break Mesh HUDs, etc.) to lose whatever "important" info was saved when the script was suspended and copied to inventory. That's pretty fragile.

this is true

where is most useful I think is in the circumstances that Wulfie describes, short life rezzed objects like bullets, fireworks, etc that obtain data for their effects from the rezzer

  • Like 1
Link to comment
Share on other sites

1 hour ago, Love Zhaoying said:

I see many, many uses for it. But, like with a lot of modern products - all you'd have to do is "reset all scripts in object" (like when people break Mesh HUDs, etc.) to lose whatever "important" info was saved when the script was suspended and copied to inventory. That's pretty fragile.

Everything can be accounted for with the right design, including everything Rolig listed. Whether or not you know/want to change your style of logic is something only you can choose, but it's natural for me to only initialize scripts once (with default values and in state_entry) with the data they need, and only do things in on_rez that would make sense to do if the object is being rezzed by a script. (Owner-change-reset should be done in the Changed event, not on_rez.)

For most combat-related things, on_rez does very little. There's basically nothing you can't do before/after on_rez. For especially short-lived objects (< 2 seconds), I tend to put a check in on_rez to see if the param is nonzero. Only scripts can give a nonzero parameter to a rezzed object, so that helps me disable whatever normal behavior the script has whenever I manually rez the object to edit it. (Which happens very frequently...)

Edited by Wulfie Reanimator
  • Like 1
Link to comment
Share on other sites

Before this all came up, I was working on a thing that rezzes stuff and coincidentally wondered what would happen if the rezzed object's listener was already open in state_entry, so I'm glad to have that answered. I'm sure this will be useful in many applications.

I realize that the reason I didn't get 'round to trying it at the time was that I was habitually laboring under many layers of obsolete paranoia. In ancient times, one would always create a random integer channel to chat on because there was no such thing as llRegionSayTo (to be sure you were only heard by the guy in your object_rez), and no OBJECT_REZZER_KEY (to be sure you weren't being led astray by anyone other than your rezzer). So I was doing this crazy handshake on a random channel passed as start parameter to the rezzed object -- even though the rezzed object was only listening to its OBJECT_REZZER_KEY and the rezzer was only talking via llRegionSayTo -- which isn't "belt and suspenders," it's just accreted stupidity.

Thinking about this particular code, though, I realize I was only setting up the channel to communicate the key of a toucher. I can live with the risk of a collision every few millennia and pass an integer start parameter hash of the toucher's key, a la 
(integer)("0x"+ llGetSubString(llDetectedKey(0), 0, 7))
then the rezzed object can run a quick llSensor() pass and match the necessarily nearby toucher agent.

That's maybe a little extra work for the rezzed object compared to just parsing a listen() event, but it means the rezzer can truly rez-and-forget, not even needing an object_rez() handler.

 

Link to comment
Share on other sites

8 minutes ago, Wulfie Reanimator said:

(integer)("0x" + llDetectedKey(0)) 😉

I think you're onto something there, but I seem to need an explicit (string) cast of llDetectedKey(0) in order for that "+" operator to work, whereas inside an llGetSubString() the implicit cast appears to work. That said, the internal "punctuation" of the UUID sure is handy; I'm guessing it's not coincidental that the first 32 bits gets separated from other stuff in the UUID.

Link to comment
Share on other sites

16 minutes ago, Wulfie Reanimator said:

(integer)("0x" + llDetectedKey(0)) 😉

I'm curious, given an integer in our scripting system is a signed 32-bit, are you saying that an implicit truncation of everything past the 8th character of the (string) version of the key will take place? Or are you saying in your quest for speed, you find dropping the substring call improves performance?

 

LOL., usually it's Rolig who pips me at the post (bad pun, sorry) today I bin Qie'ed @)

 

 

Edited by Profaitchikenz Haiku
Link to comment
Share on other sites

1 hour ago, Qie Niangao said:

I think you're onto something there, but I seem to need an explicit (string) cast of llDetectedKey(0) in order for that "+" operator to work, whereas inside an llGetSubString() the implicit cast appears to work.

Ah yeah, sorry, I misremembered that you do actually need to typecast it to string first, eg:

integer int = (integer)("0x" + (string)llDetectedKey(0));
llOwnerSay((string)int);
1 hour ago, Qie Niangao said:

That said, the internal "punctuation" of the UUID sure is handy; I'm guessing it's not coincidental that the first 32 bits gets separated from other stuff in the UUID.

1 hour ago, Profaitchikenz Haiku said:

I'm curious, given an integer in our scripting system is a signed 32-bit, are you saying that an implicit truncation of everything past the 8th character of the (string) version of the key will take place? Or are you saying in your quest for speed, you find dropping the substring call improves performance?

When you typecast a string, it will be parsed as a number. The type of number is determined by what's in front of the string.

  • A string that begins with "0x" will be interpreted as a hexadecimal number, it will keep reading the number until a character other than 0-9 and A-F is encountered.
  • A string that begins with a number will be read until a character other than 0-9 is encountered.
int = (integer)"0x779e1d56-5500-4e22-940a-cd7b5adddbe0";
llOwnerSay((string)int); // 2006850902

int = (integer)"0x779e1d56?5500-4e22-940a-cd7b5adddbe0";
llOwnerSay((string)int); // 2006850902 (same as above)

int = (integer)"0x779e1d56a5500-4e22-940a-cd7b5adddbe0";
llOwnerSay((string)int); // -1 (0x779e1d56a5500 is too big)

int = (integer)"779e1d56";
llOwnerSay((string)int); // 779 (e is not a valid digit for regular numbers)

int = (integer)"0b1111";
llOwnerSay((string)int); // 0 (but in some compilers this would be 15, "0b" means "binary")

int = (integer)"0001111";
llOwnerSay((string)int); // 1111 (leading zeroes are ignored)

And of course, any amount of function calls you can take out of your code is a performance improvement. With some limits.

Edited by Wulfie Reanimator
  • Like 1
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 389 days.

Please take a moment to consider if this thread is worth bumping.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...