Talk:Jump Teleport

From Second Life Wiki
Revision as of 23:37, 26 September 2022 by Gwyneth Llewelyn (talk | contribs) (Reformatted many paragraphs using new functionality, since <lsl> has been deprecated, and these comments are extremely important, since they convey so much informative, from a historical as well as contemporary perspective.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Just happened by this page browsing for something else for someone and thought I should comment.

Teleporters in Second Life have been plagued with the use of warpPos function which, although it is supposed to accomplish a simple task, the implementation is obfuscated with magic numbers, the design is very poor and funny effects are achieved. Here is a rundown of the problems with warpPos:

First off, at the time it was written, "teleporting" was accomplished by sitting on a prim, and having it cycle through llSetPos 10m hops in a loop until llGetPos was very close to the destination, like so:

OLD_teleport(vector dest) {
   while (llVecDist(llGetPos(),dest) > 0.1)
     llSetPos(dest);
}

The movement was slow and jerky, and could take many seconds to complete long jumps, due to the enforced 0.2s delay on llSetPos. Then, llSetPrimitiveParams was introduced, and Keknehv Psaltery discovered a rather amazing side-effect of it: when you put multiple PRIM_POSITION rules in a list, it would accumulate them and move the prim in one go. Each rule still had the 10m limit check, but it didn't apply the prim position move until it finished parsing the rule list, so it got around that issue. It was a fantastically useful and positive change, despite it essentially being a hack -- an endrun around LL's imposed limitations on prim movement.

After he published the discovery, other folks (Strife, myself, and others) found ways to optimize the script for speed and to make sure to keep the number of generated rules to a minimum. Optimizing code has the unfortunate side-effect of making it more difficult to understand, but the goal was to make something that people could use with some degree of confidence that it was using the least resources possible.

When you say the "design is very poor", I think you may be judging it a) without knowing its history, and b) by comparing it to a more modern standard (6 years later, in fact). Also, pointing out obfuscation in highly-optimized code is a priori -- that's fairly typical. There have been a number of dissections and explanations of the code posted in various forums over the years for neophyte scripters, although there is little in it which should be opaque to scripters of intermediate skill. However, I am happy to address it here, once again, for the reader's satisfaction. :)

The code mentions:

    // Try and avoid stack/heap collisions
    if (jumps > 411)
        jumps = 411

with no explanation whatsoever about what that means and why an overflow occurs.

Again, the goal of the optimized version of the code wasn't to educate, but to provide a useful, efficient, and effective drop-in function for people to use. Even still, it isn't difficult to discern why an overflow might occur from looking at the code, hence:

     while ( ( count = count << 1 ) < jumps)
         rules = (rules=[]) + rules + rules;   //should tighten memory use.

It is building a list of rules, pretty much by doubling the size of the list every iteration through that loop. When it was written, Mono didn't exist yet, so each script had only 16k of memory. Building big lists like this was hazardous, and could easily cause out-of-memory errors for even a moderately long teleport. Thus, limiting the size of the list was pretty important, so that check was added in an attempt to curb the occurrence of such errors. The original limit was much lower, but once Mono came out, it allowed for longer jumps. The "magic number" originally was based on keeping the script within the 16k limit, but with Mono's quadrupling of available memory, it became more of an "oops" check. You see, non-physical movement is only possible up to 4096m in the Z. Thus, the largest non-physical teleport length is from <0.0, 0.0, 0.0> to <256.0, 256.0, 4096.0>, or approximately 4112m, which means that, with 10m jumps, the maximum would be ~411 jumps. So even though 411 jumps won't necessarily cause Mono to run out of memory (with a simple TP script, anyway), it prevents someone who might accidentally specify a far larger teleport distance from causing their script to crash in this function. Out of memory errors are notoriously difficult to debug, so reducing one avenue of cause was a worthwhile endeavor.

warpPos calculates the number of jumps necessary and then proceeds to jump 10 meters in the calculated direction without even accounting for affinity.

Unfortunately, I have no idea what "affinity" refers to in this context, so I can't really address this issue. However, WarpPos doesn't jump 10m at a time, it actually jumps the entire distance in a single jump -- in a single llSetPrimitiveParams call.

warpPos is based on a function that executes llSetPos calls which has the effect of timing out on OpenSim due to the timeout imposed on event handlers: an event handler just goes away after some time and warpPos will fail for long distances.

While I can't address issues related to using WarpPos on OpenSim (as it wasn't written or intended for use on OpenSim), I can state that it does not (originally) use llSetPos. In more recent iterations of it, someone added a "fallback" to the OLD_Teleport method, which uses llSetPos, mainly to get around the Lindens repeatedly "breaking" the llSetPrimitiveParams hack over the years, so they don't have to field lots of angry customer issues every time. Since WarpPos was an exploit of an unintended edge-case, Linden Lab had no responsibility to "fix" it if they "broke" it (except to quell massive resident backlash every time they did), it was conceivable that, at some point, they would leave it "broken", which meant that a very large number of items all over the grid would stop working. So, for some people, a fallback to the "old tried and true" method was prudent to add afterwards, just in case.

Lastly, I would like to point out that, as of the writing of your original article, WarpPos was already functionally obsolete. Linden Lab added llSetRegionPos(), which pretty much completely deprecated WarpPos. Even still, WarpPos is a lavaflow -- LL has broken it a number of times, but has "fixed" it, effectively making it a de facto supported feature. As one of the original scripters who worked on WarpPos, I tell everyone that it is obsolete and not to use it anymore (and have since llSetRegionPos hit the grid), so any issues with it in SL are effectively moot. Issues with OpenSim I can't speak to, but I would imagine that llSetRegionPos functionality has been implemented in the last two years, so even Jump Teleport may also be effectively obsolete as well.


Now, to be fair, I would like to offer a critique of your method. :)

The following code should solve those problems

OK, so to be clear, these are the problems you are addressing with your version:

  1. Use of undocumented "magic numbers"
  2. Jumping without accounting for "affinity"
  3. OpenSim event handler timeout issue

by using vector calculus

Really? O.o

"Vector calculus (or vector analysis) is a branch of mathematics concerned with differentiation and integration of vector fields, primarily in 3-dimensional Euclidean space {R}^3."

OK, so you probably didn't use the right word there, but no matter. I'll let you off the hook for that one. ;)

and calculating 10 meters in the direction towards the final destination

That sounds familiar. :)

(the math is explained in the Second Life category).

*chuckles*

The function doing that is jumpGates which recursively computes hops towards the final destination and returns them in a list

Oh dear. :-/ Recursive functions are an interesting CompSci source code "trick", but they are rarely efficient, and are often fraught with causing more frequent out-of-memory issues than using contextual looping.

which we process later using a timer. By using a timer we make sure that the handler does not time out and we call that timer using the minimal float value (1.175494351E-38) since there are no limitations on the minimal timer interval.

Now, this part is a rather ingenious solution to the OpenSim event timeout issue, if I understand it correctly.

When jumping to the next gate, we additionally use the list as a stack in order to pop off the first coordinates and discard them from the list entirely. This makes the script start with an initial memory pool and, as it runs, it frees up memory instead of consuming more and more memory.

Well, while the function is in operation, it shouldn't use more memory; once you've built the jump list, it shouldn't grow beyond that. Also, repeated list manipulation is very heavy in terms of overhead -- LSL list manipulation functions do a lot of copying (which can cause out of memory conditions as well), which makes them quite slow. In a time- (as well as resource-)constrained environment, it might have been better to just use a "stack pointer" during the jumps, and then just free the list all at once at the end of the move.

Now, with regards to the actual code, you seem to have addressed the first issue ("magic numbers") adequately. You code would definitely qualify as an educational tool (in more ways than one, to be sure ;) ). I would argue that using the "smallest float" is a bit unnecessary in a practical sense; 0.001 (1 millisecond) is probably fast enough for practical use in either SL or OpenSim, for the life of those virtual worlds. If you really wanted to be "sure", using 1.0E-6 or 0.000001 would have been more than sufficient for any possible practical use in the lifetime of any virtual world for the next decade or two.

Regarding the second issue, since I don't understand the reference ("affinity"), I can't determine if your code meets that criterion or not.

Regarding the third issue, as I observed above, it appears to be an ingenious solution to that particular problem, so kudos for that, if it worked out. :)

The remaining misgivings I have are to the (unnecessary and expensive) use of recursion, and not addressing LSL's typically poor list management issues. More often than not, I would expect this code to crash with stack/heap issues, especially for long jumps (in fact, testing it under mono, in a script with just the jumpGates() recursive function and a basic call to it tends to fail on jumps above 3600m). Also, it appears the function always leaves you 1 meter short of your destination.

Using multiple states, beyond unnecessarily using up more memory, also requires a second expensive call to jumpGates(), when you could simply use a stack pointer/index and go in the reverse direction -- from the end of the list back to the beginning. Granted, WarpPos would also require a second list calculation/construction, but if the goal was to make a better WarpPos, might as well go with your strengths, no? (though, technically, WarpPos could be made to do the same thing with some minimal changes, but that's outside the scope of this critique).

That's pretty much all I see in terms of issues in your design and code. The only other issue I would point out is that you need to review your licenses -- the only people who can use your code are people who are also making and releasing open-source products, also under GPL v3.0. I think a more appropriate license would either be LGPL or MIT. However, that's entirely your call; just something to think about.

In closing, I would recommend to all who come after to consider using llSetRegionPos and NOT use WarpPos or similar kinds of functions (like this one), as they have been functionally obsoleted by it.


Now that I have had time to think about it, to solve the OpenSim issue really doesn't require a jump list at all. This script should effectively do the same thing on OpenSim:

// This code is an example -- no license is implied or claimed, treat as public domain by me, the author, Talarus Luan
vector  vStartPos;
vector  vEndPos;
integer bReverse;
float   fDelay = 0.001; // Very fast timer - not normally recommended
float   fDestEpsilon = 0.1; // "Fuzzy" destination distance to determine if we've arrived
                            // llSetPos and its ilk are not guaranteed to get to exact positions, so a fuzzy comparison is necessary
float   fJumpDistance = 10.0; // Max SetPos jump distance per hop

default
{
    state_entry() {
        llSitTarget(<0,0.0,1>, ZERO_ROTATION);
    }
    changed(integer change) {
        if (change & CHANGED_LINK) {
            if(llAvatarOnSitTarget()) {
                vStartPos = llGetPos();
                vEndPos = (vector)llGetObjectDesc();
                bReverse = false;
                llSetTimerEvent(fDelay);
            }
        }
    }
    timer() {
        key    kAvatar;
        float  fRemainingDist;
        vector vNextPos = ZERO_VECTOR;

        if (bReverse) {
            fRemainingDist = llVecDist(llGetPos(),vStartPos);
            if (fRemainingDist < fDestEpsilon) {
                bReverse = !bReverse;
                llSetTimerEvent(0.0);
            } else {
                if (fRemainingDist > fJumpDistance)
                    vNextPos = llVecNorm(vStartPos-vEndPos) * fJumpDistance
                else
                    vNextPos = vStartPos;
            }
        } else {
            fRemainingDist = llVecDist(llGetPos(),vEndPos);
            if (fRemainingDist < fDestEpsilon) {
                bReverse = !bReverse;
                kAvatar = llAvatarOnSitTarget();
                if (kAvatar) llUnSit(kAvatar);
            } else {
                if (fRemainingDist > fJumpDistance)
                    vNextPos = llVecNorm(vEndPos-vStartPos) * fJumpDistance
                else
                    vNextPos = vEndPos;
            }
        }
        if (llVecMag(vNextPos) != 0.0)
          llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POSITION, vNextPos]);
    }
}

Note: Untested, so there may be a bug or two in it. Will revise as needed. :)

Talarus Luan 12:43, 29 June 2014 (PDT)

a good response, Talarus. However Kira Komarov got kicked out of slWiki some time ago (tho' probably lurking as some Alt). Unfortunately some people decided to resurrect some of her pages that she had attempted to erase, many of which are not worth the paper they are written on IMHO.
A couple of comments:-
1) In SL at least, the minimum effective timer interval using llSetTimerEvent() is 2 frames, or about 44 mS.
2) WarpPos always had a little buglet in that the line
    llSetPrimitiveParams( rules + llList2List( rules, (count - jumps) << 1, count) );
tries to access an element off the end of the list, fortuitously without mishap. The line could be better coded as
    llSetPrimitiveParams( rules + llList2List( rules, (count - jumps) << 1, -1) );

Omei Qunhua 08:06, 30 June 2014 (PDT)

A few were worth saving and it was an all or nothing thing (they did a wiki rollback so all evidence of that fiasco is gone now). So, since Kira is gone any article in Category:Wizardry and Steamworks can be viciously edited, rebuilt or re-purposed without worrying about the original author objecting (it might even make them happy). -- Strife (talk|contribs) 17:00, 30 June 2014 (PDT)