The following section is motivated by a few requests that we have received to demonstrate what the main portability problems are between Linden's implementation of LSL and OpenSim's implementation of LSL.
OpenSim, on the other hand, went a different way. Instead of fixing bugs and focusing on implementing Linden's specification, it has turned into a larger codebase where the developers dump new features that themselves may contain other, new and interesting bugs.
Aside from physics that are completely unusable on OpenSim (which sort of takes the fun away from a Virtual World), OpenSim suffers from low-level implementation bugs that have never been addressed (for years) and we believe that they will not be fixed too soonly.
Second Life | OpenSim |
---|---|
7 | 6 |
SecondLife | OpenSim | Discovered |
---|---|---|
Broken | Working | 22nd of August 2013 |
The following code scans for agents within a 10m
range:
default { state_entry() { llSensor("", "", AGENT, 10, TWO_PI, 1); } sensor(integer num) { llSensorRemove(); llOwnerSay((string)llDetectedKey(0)); } }
The documentation says that the llSensor
call should trigger the sensor
event handler and detect all primitives and agents within range. However, the code above does not seem to work, the culprit being llSensorRemove
which has no purpose, nor defined meaning, within the sensor
event handler.
SecondLife | OpenSim | Discovered |
---|---|---|
Working | Broken | cca. 2012 |
default { state_entry() { if(1) { // any block: while, for, etc. integer i = 2; } integer i = 3; } }
In the Linden implementation of LSL, variables are bound to the scope that they are declared in. In the OpenSim version, this bails out with an error saying that the second declaration of the variable i
will give a different meaning to the variable i
. This is most likely because of OpenSim's closeness to C#, for example:
using System; namespace Test { class Test { public static void Main() { if(1) { integer i = 1; } integer i = 2; Console.WriteLine("Hello"); } } }
Returns the same error as the LSL snippet above. This applies to overloading event handler variables as well.
SecondLife | OpenSim | Discovered |
---|---|---|
Working | Broken | cca. 2011 |
As one of the first discoveries of Wizardry and Steamworks, mentioned on the LSL FUSS page on using the empty list in a conditional, the following snippet:
default { state_entry() { list a = [ 1, 2, 3, 4 ]; do { a = llDeleteSubList(a, 0, 0); } while(a); llOwnerSay("list a, contains: " + (string)llGetListLength(a) + " elements"); } }
fails to compile on OpenSim1).
So does:
default { state_entry() { list a = []; if(a) { llOwnerSay("not empty"); return; } llOwnerSay("empty"); } }
which bails out claiming that the list a
cannot be converted to bool
(C# again), although there is no boolean datatype in LSL.
SecondLife | OpenSim | Discovered |
---|---|---|
Working | Broken | 22nd of August 2013 |
default { state_entry() { string flag; llOwnerSay(flag = "Hello"); } }
they all fail to compile on OpenSim… This makes tricks, such as using llSensorRepeat as a second timer impossible.
In Linden's implementation, the stack and the heap are coalesced together with the code into one big pile of goo. OpenSim however, seems to separate the two, such that:
float recurse(float a) { return recurse(llPow(a, 3.402823466E+38)); } default { state_entry() { recurse(2); } }
bails out with a stack overflow error - note that this is not a stack/heap overflow, perhaps indicating that they are not coalesced in OpenSim as they are on Second Life.
On the other hand:
default { state_entry() { float a = 3.402823466E+38; do { a = llPow(a, 3.402823466E+38); } while(1); } }
lags the server very badly, sometimes ending up [making the script engine give up] in errors such as:
(0): Couldn't find assembly 'OpenSim.Region.ScriptEngine.Shared, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
which just indicates that DoS
attacks based on resource exhaustion are possible (perhaps using multiple threads / scripts). This also seemed to be the main attack pattern for Jaine Mariolack mentioned on several websites.
SecondLife | OpenSim | Discovered |
---|---|---|
Interesting | Interesting | 23nd of August 2013 |
Event handlers seem to be under different scoping rules than other blocks. This seems to be both true for SecondLife and for OpenSim.
For example, the following snippet:
changed(integer change) { if(change & CHANGED_INVENTORY || change & CHANGED_OWNER) llResetScript(); }
does compile.
On the other hand, the following example, does not:
changed(integer change) if(change & CHANGED_INVENTORY || change & CHANGED_OWNER) llResetScript();
This is most likely because event handlers have dynamic scoping and are evaluated at runtime. This is different from other type of blocks that are evaluated at compile-time.
SecondLife | OpenSim | Discovered |
---|---|---|
Broken | Working | cca. 2012 |
In SecondLife, when creating a blank notecard called News
and dropping it into the inventory of an object, the following script:
default { state_entry() { llGetNotecardLine("News", 0); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } dataserver(key id, string data) { } }
throws an error claiming that it could not find the notecard. However, if the notecard created in the inventory contains at least a character, the script is able to find it. The same does not apply to llGiveInventory
that correctly hands out the News
notecard.
SecondLife | OpenSim | Discovered |
---|---|---|
Working | Broken | cca. 2011 |
OpenSim seems to be slow to process event handlers, for example if we take the bistable color primitive script and compare the speed it takes to switch from one color to the other on both SecondLife and OpenSim, you will notice that the OpenSim version is much slower.
This just demonstrates the inefficiency of the OpenSim code and we believe that terraforming is yet another instance of the same problem - perhaps a threading problem?
The problem is fixed by Wizardry and Steamworks as a patch to OpenSim.
SecondLife | OpenSim | Discovered |
---|---|---|
Working | Broken | cca. 2011 |
OpenSim allows users to control the behavior of event handlers. There are two settings in the configuration file that allows to tweak event handlers:
MaxScriptEventQueue
).EventLimit
).
The following code illustrates the behavior by assigning a large (yet within bounds) number to an integer and then counting down and moving the prim between two coordinates. The time is not even printed out and the script just dies after EventLimit
seconds.
default { state_entry() { llGetAndResetTime(); integer i = 2147483648; do { llSetPos(<135.811096, 133.027771, 23.292763>); llSetPos(<135.811096, 132.216675, 23.292763>); llOwnerSay((string)i); } while(--i>-1); llOwnerSay((string)llGetAndResetTime()); } }
This unlike Linden's Second Life and, security aside, there is absolutely no reason for these settings to exist because they break compatibility with Linden's implementation. For example, objects that are meant to generate geometric dispositions such as the planar tile generator or jewelry generators will break unless they use asynchronous event handlers such as timer
.
SecondLife | OpenSim | Discovered |
---|---|---|
Working | Broken | cca. 2011 |
Simplest forms of physics tests, such as dropping the script bellow and assuming that both OpenSim and SecondLife use 9.81m/s
as gravity:
default { state_entry() { llSetStatus(STATUS_PHYSICS, TRUE); llSetForce(<0,0,9.81> * llGetMass(), TRUE); } }
will indicate that OpenSim's implementation is broken and that the script makes the primitive that it is placed in rise rather than keeping it motionless - even further attempts to hold it in-place by editing the object will still make the object rise.
On SecondLife however, the two calls do not seem to be atomic, which make building certain simulations such as the Great Wanderer difficult.
That is because by dropping the script above into a primitive, or just restarting the script will turn physics on, at which point the primitive receives a downward acceleration and by the time the counter-force (using llSetForce
) kicks in, the object already has a downward motion. The only way around this problem, is to either:
A more sensible solution would be to make those two functions atomic - in order to prevent the race condition.
SecondLife | OpenSim | Discovered |
---|---|---|
Broken | Not sighted | cca. 2011 |
Sometimes reproduced by:
(1)
several times.This is a long-time standing Second Life bug that seems to plague scripted objects. For no reason that is easy to determine, after a certain number of cycles, the primitives seems to remain unresponsive so that even if the script is recompiled, deleted or substituted for something else, the primitive simply does not perform the actions in the script.
The only solution, is to create a new primitive and copy the script over and to dispose of the "broken" primitive.
SecondLife | OpenSim | Discovered |
---|---|---|
Broken | Working | 18th of October 2013 |
The following code:
default { state_entry() { string sa = "12999"; list la = llParseString2List(sa, [], ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]); llOwnerSay("la: " + llDumpList2String(la, ",")); string sb = "abzzz"; list lb = llParseString2List(sb, [], ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]); llOwnerSay("lb: " + llDumpList2String(lb, ",")); } }
produces the output:
[16:52] Primitive: la: 1,2,9,9,9 [16:52] Primitive: lb: a,b,z,z,z
on OpenSim, and on Second Life, the output is the following:
[16:50] Object: la: 1,2,999 [16:50] Object: lb: a,b,zzz
which would indicate that the Second Life LSL implementation of llParseString2List
is broken. If the list is shortened, such as:
list lb = llParseString2List(sb, [], ["a", "b", "z"]);
we get the correct result a,b,z,z,z
on both OpenSim and Second Life.
SecondLife | OpenSim | Discovered |
---|---|---|
Broken | Working | 20th of October 2013 |
Given the following code:
default { state_entry() { list a = [ 1, 2, 3 ]; integer k = (a!=[]); llOwnerSay((string)k); } }
the reasoning is that the integer k
will be set to the truth value of (a!=[])
(since this is not an object-oriented language with references, the meaning of a!=[]
is the list a is not empty). So the expected outcome of the program would be that k=1
. This works fine on OpenSim but in SecondLife, the code returns the length of list a
- which is absolutely nonsense.
As a counter-example, the previous reasoning holds for the following snippet:
default { state_entry() { integer k = 3 > 1; llOwnerSay((string)k); } }
which means that the integer k
will be set to the truth value of 3>1
. In this case, the expected outcome k=1
can be observed on both OpenSim and Second Life.
SecondLife | OpenSim | Discovered |
---|---|---|
Broken | Broken | N/A by design |
llList2CSV([ "a", "b" ]);
is functionally equivalent to:
llDumpList2String([ "a", "b" ], ", ");
Similarly:
llCSV2List("a, b");
is functionally equivalent to:
llParseString2List("a, b", [ ", " ], []);
which is absolute nonsense by any CSV standard (ie: RFC 4180) and makes llList2CSV
and llCSV2List
redundant.
A list such as [ "something, something", "is broken" ]
will not survive a llList2CSV
/ llCSV2List
roundtrip.
Wizardry and Steamworks have an implementation of an RFC 4180 compliant set of CSV functions.
SecondLife | OpenSim | Discovered |
---|---|---|
Broken | Broken | N/A by design |
The functions llEscapeURL
and llUnescapeURL
are, in fact, URI escape functions instead of URL escape functions as their name would erroneously imply.
The misleading nomenclature leads to issues perceived in other languages as well such as PHP or, lately, node.js.
Furthermore, since these functions are bound to be used given llHTTPRequest
or http_request
/ http_response
, neither llEscapeURL
nor llUnescapeURL
do not provide an application/x-www-form-urlencoded encoding. In other words, llEscapeURL
and llUnescapeURL
cannot be used to POST form data.
Wizardry and Steamworks has created a set of LSL functions that perform application/x-www-form-urlencoded
encoding and decoding.
llSetStatus