JavaScript semantic change

Discussion and information relevant to creating special missions, new ships, skins etc.

Moderators: another_commander, winston

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

JavaScript semantic change

Post by JensAyton »

Boo.

For a while now – call it a year and a half – I’ve been putting off upgrading Oolite’s JavaScript engine to the current version. The amount of work has of course grown constantly during that time, and there are now some compatibility issues.

In particular, it is no longer possible for Oolite to customize the behaviour of the ==/!= operators to usefully compare different objects with the same values. Doing this in the first place was an abuse of the engine which didn’t comply with JavaScript language semantics. This primarily affects vectors and quaternions.

I’m loathe to add equal() methods to vectors and quaternions, though, because they’re less meaningful than you’d expect. Because of the limited precision of floating-point numbers, calculations that are algebraically equivalent can produce slightly different results (or in odd cases, very different results). The correct way to compare vectors is to check whether the distance between them is below some threshold value:

Code: Select all

var equal = v.squaredDistanceTo(u) < thresholdSquared;
For quaternions, the dot product of two normalized quaternions will be near 1 if they are nearly the same.

Code: Select all

// Skip normalize() if they are known to be normalized already –
// entity orientations are always normalized.
var equal = q.normalize().dot(p.normalize()) > 0.9999;
As the functionality is no longer present in SpiderMonkey, it’s not possible to provide a transition period. ==/!= will not work for distinct Oolite objects in the next release, and will stop working in nightlies soon (probably tonight) even before the upgrade is complete.
Last edited by JensAyton on Thu Dec 30, 2010 10:24 am, edited 1 time in total.

User avatar
DaddyHoggy
Intergalactic Spam Assassin
Intergalactic Spam Assassin
Posts: 8501
Joined: Tue Dec 05, 2006 9:43 pm
Location: Newbury, UK
Contact:

Post by DaddyHoggy »

Welcome back Ahruman - for those who script (not me) I wish it was with better news!

(Although compatibility, in the long run, is always a good thing)
Selezen wrote:Apparently I was having a DaddyHoggy moment.
Oolite Life is now revealed here

User avatar
Svengali
---- E L I T E ----
---- E L I T E ----
Posts: 2370
Joined: Sat Oct 20, 2007 2:52 pm

Post by Svengali »

Heija - welcome back, Ahruman.
Hope you've had a good time without hazzles.
Ahruman wrote:As the functionality is no longer present in SpiderMonkey, it’s not possible to provide a transition period. ==/!= will not work for distinct Oolite objects in the next release, and will stop working in nightlies soon (probably tonight) even before the upgrade is complete.
Does it mean we can already test our OXPs in trunk? Or should we wait some days...?

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

Svengali wrote:Does it mean we can already test our OXPs in trunk? Or should we wait some days...?
The equalspocalypse landed in r3849 an hour ago.

Switeck
---- E L I T E ----
---- E L I T E ----
Posts: 1998
Joined: Mon May 31, 2010 11:11 pm

Post by Switeck »

Will it break this?:

if (!system.isInterstellarSpace && !player.ship.docked && (player.ship.speed > 10*player.ship.maxSpeed || timeAccelerationFactor > 4.0) && !player.alertHostiles && system.shipsWithRole("station", player.ship, 150E3).length < 1 && system.shipsWithRole("station", player.ship, 1500E3).length > 0 && missionVariables.deep_space_pirate_kills < (30 + system.countShipsWithRole("pirate-cove")*20 - system.government*3 ) )
this.addPirate()

(...from my modified+hacked-to-death Deep Space Pirates OXP)

User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Re: JavaScript semantic change

Post by Eric Walch »

Ahruman wrote:The correct way to compare vectors is to check whether the distance between them is below some threshold value:

Code: Select all

var equal = v.squaredDistanceTo(u) < thresholdSquared;
There is no comparable metric for quaternions in the current scripting interface. If you’re comparing quaternions, tell me why.
I have compared quaternions for the player.ship to see if it did not change orientation for a certain time. Instead of that I could compare two vectors to achieve the same goal. e.g. vectorUp and vectorRight.

I did add those vectors as ship properties so there is no need to re-calculate them from a quaternion for ships.

User avatar
Commander McLane
Intergalactic Spam Assassin
Intergalactic Spam Assassin
Posts: 9520
Joined: Thu Dec 14, 2006 9:08 am
Location: a Hacker Outpost in a moderately remote area
Contact:

Post by Commander McLane »

First of all: Glad to hearing from you after a long hiatus, Ahruman! :D


Switeck wrote:Will it break this?:

if (!system.isInterstellarSpace && !player.ship.docked && (player.ship.speed > 10*player.ship.maxSpeed || timeAccelerationFactor > 4.0) && !player.alertHostiles && system.shipsWithRole("station", player.ship, 150E3).length < 1 && system.shipsWithRole("station", player.ship, 1500E3).length > 0 && missionVariables.deep_space_pirate_kills < (30 + system.countShipsWithRole("pirate-cove")*20 - system.government*3 ) )
this.addPirate()
Well, it seems to contain neither "==" nor "!=" (which are the only comparators explicitly mentioned). So I would guess the answer is no.

However, I can't claim that I've already understood what exactly will change has changed.
Ahruman wrote:In particular, it is no longer possible for Oolite to customize the behaviour of the ==/!= operators to usefully compare different objects with the same values. Doing this in the first place was an abuse of the engine which didn’t comply with JavaScript language semantics. This primarily affects vectors and quaternions.

<snip>

==/!= will not work for distinct Oolite objects
I need some clarification here. Am I right in assuming that comparing vectors and quaternions with "==" and "!=" will no longer work (what about "===" and "!=="?), but something like "system.ID == 7" is still okay? Or not, because it's an Oolite object as well?

My confusion is with the exact meaning of "compare different objects" / "distinct Oolite objects". Does it mean that ==/!= is now illegal for use in any Oolite object class (therefore basically everywhere, except in general JS objects like Math), or does it mean that ==/!= is only illegal when comparing two different object with each other (like a vector to a string)?

Sorry for my confusion. I am not enough of a programmer to immediately grasp the concept.
Last edited by Commander McLane on Sun Dec 12, 2010 11:42 pm, edited 1 time in total.

User avatar
DaddyHoggy
Intergalactic Spam Assassin
Intergalactic Spam Assassin
Posts: 8501
Joined: Tue Dec 05, 2006 9:43 pm
Location: Newbury, UK
Contact:

Post by DaddyHoggy »

Commander McLane wrote:
Switeck wrote:Will it break this?:

if (!system.isInterstellarSpace && !player.ship.docked && (player.ship.speed > 10*player.ship.maxSpeed || timeAccelerationFactor > 4.0) && !player.alertHostiles && system.shipsWithRole("station", player.ship, 150E3).length < 1 && system.shipsWithRole("station", player.ship, 1500E3).length > 0 && missionVariables.deep_space_pirate_kills < (30 + system.countShipsWithRole("pirate-cove")*20 - system.government*3 ) )
this.addPirate()
Well, it seems to contain neither "==" nor "!=" (which are the only comparators explicitly mentioned). So I would guess the answer is no.
I did think the same myself, but declined to point out the obvious flaw in Switeck's post :wink:
Selezen wrote:Apparently I was having a DaddyHoggy moment.
Oolite Life is now revealed here

Switeck
---- E L I T E ----
---- E L I T E ----
Posts: 1998
Joined: Mon May 31, 2010 11:11 pm

Post by Switeck »

I guess the other parts of DSP OXP are closer to this, such as checking to see if the player's ship is "within" the space-lanes. But even that seems to lack == or != for its checks.

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

Commander McLane wrote:I need some clarification here. Am I right in assuming that comparing vectors and quaternions with "==" and "!=" will no longer work (what about "===" and "!=="?), but something like "system.ID == 7" is still okay? Or not, because it's an Oolite object as well?

My confusion is with the exact meaning of "compare different objects" / "distinct Oolite objects".
Argh, I just lost twenty minutes of detailed explanation, so here’s the short form.

First, brush up on the difference between == and ===.

The old version of SpiderMonkey allowed us to expand the definition of “equivalence” for ==. In particular:

Code: Select all

var v = new Vector3D(1, 2, 3);
var u = v;
var w = new Vector3D(1, 2, 3);

v === u; // True, they refer to the same object.
v == u;  // True, an object is always equal to itself.
v === w; // False, they’re distinct objects.
v == w;  // Formerly true as a magic special case, now false.
Other than vectors and quaternions, Oolite defined special comparisons for JS objects that wrap native Objective-C objects, such that if two JS objects wrapped to the same ObjC object, they were equal. I don’t think this situation can actually arise, it was just a safety net.

User avatar
Commander McLane
Intergalactic Spam Assassin
Intergalactic Spam Assassin
Posts: 9520
Joined: Thu Dec 14, 2006 9:08 am
Location: a Hacker Outpost in a moderately remote area
Contact:

Post by Commander McLane »

:idea: I think I got it now (which required understanding what "object" means :oops: ).

You cannot compare two vectors to each other anymore (like, let's say, the vector origin->witchpoint to the vector origin->playership, in order to determine whether the playership is located exactly at the witchpoint). This comparison will in future always return false. This will make comparing positions a little trickier, because each position is in fact a (distinct) vector. "player.ship.position == system.mainStation.position" will in future return false when docked (provided the player ship coordinates are indeed the same as the station's coordinates when docked; but I think that is so).

So instead of comparing the two positions with each other, one should in future for instance construct the vector between these positions, and compare that vector's .length property, which still will be "== 0" or "!= 0". (And probably—due to rounding errors—it will never be "== 0", but only "< 0.01".)

But we still can compare properties of objects to whatever we want, using "==" and "!=", like in the small example above.

Right?

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

Commander McLane wrote:But we still can compare properties of objects to whatever we want, using "==" and "!=", like in the small example above.
No, not really.

To fully understand this, you need to distinguish between values and references. A value is, er, a thing, and a reference is a name for a thing. There are essentially two types of reference in JavaScript: variables and properties. There are also two fundamental types of values: primitives and objects. Any reference can refer to a primitive or an object; in particular, properties often refer to other objects.

The primitives are numbers, strings, true, false, null and undefined. Everything else is an object. (Just to confuse matters, there are Number, String and Boolean objects as well as primitive types, but let’s pretend we didn’t hear that.)

One of the special attributes of primitives is that there is conceptually only one primitive of a given type with a given value. For instance, there is only one true and one false, so === comparisons make sense for booleans:

Code: Select all

var a = false;
var b = (0 == 1);

a === b;   // True
Similarly, there is only one number primitive with the value 5:

Code: Select all

var a = 5;
var b = 2 + 3;

a === b;   // True
Another special property of primitives, which goes hand in hand with this, is that they are immutable. Attempts to set a property of a primitive are ignored:

Code: Select all

true.flavour = "strawberry";
true.flavour; // Warning: reference to undefined property true.flavour
(Offhand, I believe this is an error in ECMAScript 5 strict mode.)

The other type of value is objects. An object is a collection of properties, and by default is mutable (i.e. its properties can be changed). Two objects with identical sets of properties and values are still two distinct objects, and changing one does not affect the other:

Code: Select all

var a = new Object;
var b = new Object;

a == b;    // False; the objects are structurally identical, but == doesn’t test this.
a === b;   // False

var c = a;
a == c;    // True
a === c;   // True

a.flavour = "banana"; // The objects are no longer structurally identical.

a.flavour; // "banana"
b.flavour; // undefined
c.flavour; // "banana" — a and c refer to the same object.
Now, if life were simple we’d be able to say that an object never compares equal to anything else, but it isn’t and we can’t. Remember those objects corresponding to primitives I tried to sweep under the carpet above? The Powers That Be decided to try to hide their existence by allowing objects to be compared to numbers and strings. In particular, if you try to compare an object to a number or string primitive, the JS engine will try to call the object’s valueOf() method, then its toString() method, to get a primitive value. (In ECMAScript 5th Edition, which I expect the next release to conform to, toString() is called first when comparing to a string.) Example:

Code: Select all

var strPrimitive = "foo";
var strObjA = new String(strPrimitive);
var strObjB = new String(strPrimitive);
var customObject = { toString: function() { return "foo"; } }

// === is false for all combinations.

strPrimitive == strObjA;      // True
strPrimitive == strObjB;      // True
strObjA == strObjB;           // False!
customObject == strPrimitive; // True!
The old version of SpiderMonkey implements this using a hook that host objects (i.e., objects defined by the program running the JavaScript engine, in this case Oolite) can override. The new version does not.

So to return to an earlier question: system.ID == 7 is OK, because 7 is a number primitive, one of the types for which == is meaningful. player.ship.position == Vector3D(5, 7, 22) isn’t, because == isn’t meaningful for Vector3D objects. This is similar to how you can add numbers using +, but have to use the add() method for vectors. The next version of JavaScript may address this by adding “value types”, but that won’t be out until 2013, so we’ll be at least at test release 1.78 by then. ;-)

User avatar
Thargoid
Thargoid
Thargoid
Posts: 5510
Joined: Thu Jun 12, 2008 6:55 pm

Post by Thargoid »

One other small clarification request. Will the usage of == for vectors and quaternions now generate a script error in the logs/console, or just an "incorrect" result when compared to its past usage?

I think I can follow the meaning of the explanation above, but for upgrading OXPs do we need to check that no script errors are reported using suitable trunk versions, or is it a case of explicitly checking things are still operating correctly too in every detail?

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

Thargoid wrote:One other small clarification request. Will the usage of == for vectors and quaternions now generate a script error in the logs/console, or just an "incorrect" result when compared to its past usage?
The comparison will always evaluate to false, unless you happen to have two references to the same vector. There is no exception or error message; comparing objects is valid, just not useful.

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Re: JavaScript semantic change

Post by JensAyton »

Eric Walch wrote:
Ahruman wrote:The correct way to compare vectors is to check whether the distance between them is below some threshold value:

Code: Select all

var equal = v.squaredDistanceTo(u) < thresholdSquared;
There is no comparable metric for quaternions in the current scripting interface. If you’re comparing quaternions, tell me why.
I have compared quaternions for the player.ship to see if it did not change orientation for a certain time. Instead of that I could compare two vectors to achieve the same goal. e.g. vectorUp and vectorRight.
I’m going through Quaternion’s properties to write unit tests, and wondered what the quaternion dot product was actually good for. It turns out that it’s a good measure of difference; similar normalized quaternions have a dot product near 1. For example, oldOrientation.dot(currentOrientation) > 0.999 will detect changes of more than about five degrees around any axis.

Post Reply