Jump to content

Best Scripter Tips and Shortcuts


Lexie Linden
 Share

Recommended Posts

2 hours ago, Wulfie Reanimator said:

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.

Ah ha, so it's the "-" in the key that causes conversion to cease without there being any overflow. Neat. Qie was right then about it being no coincidence the first 32 bits are a distinct chunk?

Edited by Profaitchikenz Haiku
Link to comment
Share on other sites

3 hours ago, Profaitchikenz Haiku said:

Ah ha, so it's the "-" in the key that causes conversion to cease without there being any overflow. Neat. Qie was right then about it being no coincidence the first 32 bits are a distinct chunk?

Yes and no. It's not a coincidence.. in the sense that UUIDs are a general standard used in a lot of places, not just Second Life, so the Lindens did not come up with UUIDs with this in mind. But at the same time.. the fact that they chose this system is a happy coincidence/convenience that just happens to allow this kind of neat conversion (and maybe that's a contributing reason they chose it, but I doubt it). Even the way strings are interpreted as numbers is something seen in C/C++, so that's not specific to SL either.

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

  • 4 weeks later...
On 2/25/2020 at 12:27 PM, CoffeeDujour said:

Really have to be careful when using tools designed to make LSL mimic a bigger more connected language, very easy to end up with a large slow script that if hand optimized would be a fraction of the size and much faster.

Agreed, unfortunately as scripts grow in size and complexity, it becomes difficult to keep one's sanity without some QoL features.

  • Like 1
Link to comment
Share on other sites

  • 1 month later...
  • 1 year later...

If you want to add more prims to an object, it's not too hard to rez them and then llCreateLink. But what if you want to delete a prim from your linkset? It was a nice 'ah-hah' moment when I realized that rather than having a die script in every prim that might need to be deleted, or trying to llRemoteLoadScriptPin() you can just set them to temporary and move them out of the way:

integer addremove;
delete_link(integer n)
{   llSetLinkPrimitiveParamsFast(n,
    [   PRIM_POS_LOCAL,<0,0,-1>, // out of the way.
        PRIM_TEMP_ON_REZ, TRUE, // set to delete // setting for one prim seems to set for the whole linkset.
        PRIM_COLOR,ALL_SIDES,<0,0,0>,0.0 // invisible.
    ]);
    llBreakLink(n);
    llSetLinkPrimitiveParamsFast(llGetNumberOfPrims()!=1, // when there is only 1 prim, the root has link# 0, Ugh.
    [	PRIM_TEMP_ON_REZ, FALSE]);
}
default
{   state_entry()
    {   llRequestPermissions(llGetOwner(),PERMISSION_CHANGE_LINKS);
    }
    touch_start(integer total_number)
    {   addremove=!addremove;
        if(addremove)
        {   llRezAtRoot(llGetInventoryName(INVENTORY_OBJECT,0),llGetPos()+<0,0,1>,<0,0,0>,<0,0,0,1>,0);
        }else
        {   delete_link(2); // should do a permissions check.
        }
    }
    object_rez(key ID)
    {   llCreateLink(ID,TRUE); // should do a permissions check.
    }
}

 

  • Like 3
Link to comment
Share on other sites

  • 4 weeks later...

i will pop this in here as it comes up enough times to warrant doing so

 

often we need to keep a value within some lower (min) and upper (max) bounds. The textbook code to do this is typically

if (n < min)
   n = min;
else if (n > max)
   n = max;
// else (n >= min && n <= max)
//    n = n;

when we need to do this more than one time in our script then we often wrap this in a function, similar too

integer bounds(integer n, integer min, integer max)
{
   if (n < min) return min;
   if (n > max) return max;
   return n;
}

// usage
   n = bounds(n, min, max);

 

a alternative to a user-defined function, is a list lookup where the expression evaluates to a index in the set [0, 1, -1]

n = llList2Integer([min, n, max], (n > min) * (-(n > max) | 1));

 

which saves us about 500 memory bytes that a user-defined function incurs

Edited by Mollymews
typo
  • Like 5
Link to comment
Share on other sites

  • 3 months later...
On 3/22/2022 at 8:43 PM, Mollymews said:

often we need to keep a value within some lower (min) and upper (max) bounds.

I had a think about this again, and ended up with a slightly more efficient answer:

If our lower bound is 0, we can be really efficient:

x *= (x>0); // if (x < 0) x=0;

this can be generalized by adding and subtracting a constant from x:

x = c+((x-c)*(x>c)); // if(x<c) x=c;

for upper bounds, we get a very similar looking answer by subtracting x from a constant: (N.B. (c-(c-x))==x)

x = c-((c-x)*(x<c)); // if(x>c) x=c;
//ETA: this is actually equivalent to:
x = c+((x-c)*(x<c));
//e.g. upper and lower bounds are identical save for the conditional.

a bit of a brain teaser to work through.

Note that the conditions represent when x=x, which is the opposite of the "textbook" conditional of when to set x to the boundary.

ETA: Threading those together and re-arranging a bit leads to:

x = (x>l)*((x<u)*(x-u)+(u-l))+l; // if(x<l) x=l; if(x>u) x=u;
// example:
x = (x>3)*((x<5)*(x-5)+2)+3;
Edited by Quistess Alpha
  • Like 1
  • Thanks 2
Link to comment
Share on other sites

Also, as I think @Mollymews tested at one point, inline list construction is quite inefficient. Instead of

Value = llList2Float([a,b,c,d],i);

consider using

Value = (a*(i==0))+(b*(i==1))+(c*(i==2))+(d*(i==3));

if a,b,c,d are integers, floats or vectors, and aren't already in a list, but ~are calculated right before you select one. (If it makes sense to have the list as a global variable that is never or rarely modified, then llList2* might be as or more efficient)

Applying that to the previous post, we get:

integer x /*= ...*/; 
// clamp x to be max if x>max, and min if x<min
{ // brackets can be used to isolate the scope of a 'working variable'. In this case, we only need i for this calculation.
  integer i = (x>max)-(x<min);
  x = (min*(i==-1)) + (x*(i==0)) + (max*(i==1));
}

 

Edited by Quistess Alpha
'i' can be an index rather than a bitmask, saving a few cycles.
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

14 hours ago, Quistess Alpha said:

Also, as I think @Mollymews tested at one point, inline list construction is quite inefficient

about inline lists, was Qie prompted by Wulfie who showed that a var list is significantly more performant than a const list

they showed that is best to go with a var list as an parameter for llList2... rather than a const list
 

// do this

list args = ["No", "Yes"];

string result = llList2String(args, condition); // where condition is TRUE or FALSE

// and not

string result = llList2String(["No", "Yes"], condition); // where condition is TRUE or FALSE.

 

14 hours ago, Quistess Alpha said:
integer x /*= ...*/; 
// clamp x to be max if x>max, and min if x<min
{ // brackets can be used to isolate the scope of a 'working variable'. In this case, we only need i for this calculation.
  integer i = (x>max)-(x<min);
  x = (min*(i==-1)) + (x*(i==0)) + (max*(i==1));
}

 

 

i really really like this

about the only thing I could add to it is that when we compare a variable to a const in LSL Mono then is a bit more performant to place the const on the left. Whyam not exactly sure, something something stack handling

x = (min * (-1 == i)) + ( x * (0 == i)) + (max * (1 == i));

 

 

  • Thanks 1
Link to comment
Share on other sites

13 minutes ago, Mollymews said:

is a bit more performant to place the const on the left.

I've also heard it's better for catching = vs == mistakes: -1=i; is not a valid statement.

As for above code and miniscule improvements, I've been mulling over

x = (x>l)*((x<u)*(x-u)+(u-l))+l; // if(x<l) x=l; if(x>u) x=u;

and although a bit confusing, the general pattern allows for checks (if) and default (else{ }), and is about as efficient as possible:

// normally would write:
if(!cA)      return rA;
else if(!cB) return rB;
else if(!cC) return rC;
else if(!cD) return rD;
else         return rE;

// can instead try:

(cA)*
  ((cB)*
    ((cC)*
      ((cD)*
        (   (rE-rD)
        )+(rD-rC)
      )+(rC-rB)
    )+(rB-rA)
  )+rA;

If the return values rA, rB etc. are constants, the difference can be done in-code.

(cA)*((cB)*((cC)*(W)+X)+Y)+Z;
// if(!cA) return Z; if(!cB) return Y+Z; if(!cC) return X+Y+Z; else return W+X+Y+Z;

Compare

(x>3)*((x<5)*(x-5)+2)+3;

2 comparisons, 2 multiplications, 1 difference, 2 sums. with

{ integer i = (x>5)-(x<3);
  x = (-1==i)*3 + (!i)*x + (1==i)*5;
}

5 comparisons, 3 multiplications, 1 difference, 2 sums.

. . . but maybe this discussion would be best for an efficiency/ optimization thread . . .

  • Thanks 2
Link to comment
Share on other sites

8 hours ago, Quistess Alpha said:

maybe this discussion would be best for an efficiency/ optimization thread . . .
 

is probably better to start a new thread yes

this thread is more for quick tips/statements rather than a general discussion on inlining code

inline code is a large topic and quite varied, and the question to do or not do is often circumstantial. I think is worth having these more indepth discussion in its own thread, about the various methods and techniques that can optimise when we do inline code

and much of what we are currently discussing also touches on logic programming. So the discussion we can also have in its own thread can be about about how and where we can use logic programming methods to achieve some measurable gain for our scripts, be that in execution speed or memory savings

 

ps. Repost your last in  a new thread as is quite interesting. Is also a thing about using equality vs logical operators which is also worth talking about 

Edited by Mollymews
typs
Link to comment
Share on other sites

  • 5 months later...
On 7/3/2022 at 7:51 AM, Quistess Alpha said:

I've also heard it's better for catching = vs == mistakes: -1=i; is not a valid statement.

As for above code and miniscule improvements, I've been mulling over

x = (x>l)*((x<u)*(x-u)+(u-l))+l; // if(x<l) x=l; if(x>u) x=u;

and although a bit confusing, the general pattern allows for checks (if) and default (else{ }), and is about as efficient as possible:

// normally would write:
if(!cA)      return rA;
else if(!cB) return rB;
else if(!cC) return rC;
else if(!cD) return rD;
else         return rE;

// can instead try:

(cA)*
  ((cB)*
    ((cC)*
      ((cD)*
        (   (rE-rD)
        )+(rD-rC)
      )+(rC-rB)
    )+(rB-rA)
  )+rA;

If the return values rA, rB etc. are constants, the difference can be done in-code.

(cA)*((cB)*((cC)*(W)+X)+Y)+Z;
// if(!cA) return Z; if(!cB) return Y+Z; if(!cC) return X+Y+Z; else return W+X+Y+Z;

Compare

(x>3)*((x<5)*(x-5)+2)+3;

2 comparisons, 2 multiplications, 1 difference, 2 sums. with

{ integer i = (x>5)-(x<3);
  x = (-1==i)*3 + (!i)*x + (1==i)*5;
}

5 comparisons, 3 multiplications, 1 difference, 2 sums.

. . . but maybe this discussion would be best for an efficiency/ optimization thread . . .

I am curious if the condensed form is actually any "faster", or just takes less bytecode.

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

A very important tip I learnt in a (tangentially related) discussion about LSD:

Trigger GC using llSleep() to ensure release of "lingering objects".

Very important if your script keeps building temporary gigantic lists and/or strings.

Link to comment
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...