OXP Performance tips

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

Moderators: another_commander, winston

ashtiboy
Competent
Competent
Posts: 37
Joined: Thu Oct 29, 2015 9:41 pm

Re: OXP Performance tips

Post by ashtiboy »

ok it's fixed in 1.85 so ignore my coments
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

@Astrobe
Thanks for the links, I'll check them out.

@another_commander
another_commander wrote: Thu Oct 05, 2017 5:45 pm
Would these files need to replace the original ones as a batch or can we substitute the core files one by one? In the second case, maybe a good way to test would be to simply take the first file, substitute the original one and test for a while, looking for different behaviours and/or bugs etc. If we hit bugs, we know immediately which file to look at for fixing them. When happy with that one, put back the original file, then replace the second in the list with yours and repeat. This way, we test small changes each time and in case of problems we know that the new file we introduced will probably need further analysis. It still takes a heck of a time to test, but at least we test in smaller chunks.
The second case. Most of what I did was
  • function reference caching (eg. random = Math.random;) ->[speed]
  • property caching (eg. x = this.ship.group; if( x.leader )... ->[speed]
  • for loop boundary tests (move .length test out of for stmt) ->[speed]
  • re-use arrays & objects ->[GC]
The first 3 are limited to the function. Re-use is also function limited except for oolite-priorityai (a special case) and in the 3 oolite-contracts-*.js files & oolite-primable-equipment-manager.js, where I implemented pools for recycling objects (post is in the works). Even there, it is limited to the file. Mind you, I didn't track the cross-talk....

Ok, I did a search and found:

Code: Select all

oolite-constrictor-hunt-mission.js calls worldScripts["oolite-populator"].systemWillPopulate();
oolite-thargoid-plans-mission.js   calls worldScripts["oolite-populator"].systemWillPopulate();
oolite-cloaking-device-mission.js  calls worldScripts["oolite-populator"].systemWillPopulate();

oolite-cloaking-device-pod.js calls worldScripts["oolite-primable-equipment-register"]._updatePrimableEquipmentSettings("EQ_CLOAKING_DEVICE",true);

oolite-contracts-cargo.js      calls worldScripts["oolite-contracts-helpers"];
oolite-contracts-parcels.js    calls worldScripts["oolite-contracts-helpers"];
oolite-contracts-passengers.js calls worldScripts["oolite-contracts-helpers"];
oolite-priorityai.js           calls worldScripts["oolite-contracts-helpers"]);

oolite-tutorial-fighter.js calls worldScripts["oolite-tutorial"]._nextItem();
I take that back: neither first case or second. Some can go individually, while others in small batches:
  • oolite-cloaking-device-*.js 4 small files (Norby is currently working on one: oolite-cloaking-device-equipment.js)
  • oolite-constrictor*.js 4 small files
  • oolite-thargoid-*.js 1 small, 1 tiny (single fn call)
  • oolite-tutorial*.js 1 small, 1 medium
[scale based on 1 large (oolite-populator.js) and 1 huge(oolite-priorityai.js)]

I'm fairly certain the rest can go in one at a time. Changes in oolite-contracts-helpers are function limited and the 3 contract scripts are nearly identical.

Unfortunately, it's the large & huge ones that are most problematic, not for their size but because 'random()' is by far the most common function call in populator and priorityai is, well, behaviour (how can we quantify that?). These two will definitely need testing in isolation.

Remember, most of the changes are in syntax, not logic and they all got past JSLint. I believe (hope!) that any mistakes will either be obvious (ie. D'oh! moments) or missing that a third function also relys on an array/object that I've re-used too early (most likely in priorityai)
another_commander wrote: Thu Oct 05, 2017 5:45 pm
Because of the time that it will require for a full test snd confirmation of good working order, I think that this should probably be the first thing to look at after we release 1.86. Testing can of course begin before that, but given that it has been more than one year since the last stable release and that applying these changes now may affect stability in various and original ways, maybe it would be best to get a new stable out first.
I agree completely, release what is known to be stable. There is no great urgency here, as 4 MB over 5 minutes is pretty tame compared to some oxp's. But it's a good exercise for those involved and once published, authors will have some examples to draw from.
another_commander wrote: Thu Oct 05, 2017 5:45 pm
Regarding the deliberate changes you have made, I think it's best to wait for cim to give some input, as he was the author of all those scripts.
OK, I've restored those changes and left mine commentted out (search for 'cag:')

https://www.dropbox.com/s/cudwuq8d56epc ... s.zip?dl=0
Last edited by cag on Sat Oct 07, 2017 1:58 am, edited 1 time in total.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

As promised, part 2, object pools:

When trying to re-use arrays/object, you'll encounter a situation where they are shared among functions. If it's just a one-off instance, placing it in a this.$variable works fine. But when there are multiple instances and/or they are dynamically created, we'll need a different solution. (NB: the following applies equally to arrays and objects, so I'll use 'things' for brevity)

Rather than creating a thing directly, we have an allocating function that first checks if there are any used ones available for re-use before creating a new one.

Code: Select all

    function Pending( fn, parm ) { // constructor
        this.fn = fn; 
        this.parm = parm; 
    }
    var used_pending = [];
    function alloc_pending( fn, parm ) {    
        var event;
        if( used_pending.length > 0 ) {
            event = used_pending.pop();
            event.fn = fn;     // over-write previous info
            event.parm = parm; //   "
        } else {
            event = new Pending( fn, parm );
        }
        return event;
    }
    
    var events = [];
    ...
    //events.push( {fn: some_fn, parm: true} );     // instead of creating new ones
    events.push( alloc_pending( some_fn, true ) );  // we recycle
When we're finished with a thing, we recycle it by putting it back in the list of used ones.

Code: Select all

    function free_pending( event ) {
        if( !event ) return;
        event.fn = null;    // clear existing info
        event.parm = null;  //    "
        used_pending.push( event );
    }
    
    //event = null;            // instead of discarding when finished
    free_pending( event );     // we return it to the used list
Care must be taken so that no information is carried over from a thing's previous incarnation. You can either clear all the keys when freeing or over-write them all when you allocate (I've included both just to demonstrate). This is quite easy for arrays, just set .length = 0. If you don't, you may develope some particularly nasty bugs! :twisted: It has been suggested that for objects, using the delete statement to clear keys is preferable but I found no sigficant difference when I profiled both methods.

I'm a firm believer in the K.I.S.S. methodology (Keep It Simple, Stupid), so if you have more than one type of thing, just make a pool for each. A single pair of functions to handle a complex mix of things is just asking for trouble. The same argument if your things have things; a pool for each type and be done with it.

Between these 2 posts, I believe that's all we need to cut significantly the amount of garbage we generate (though I'd be happy to be proved wrong). We cannot eliminate it completely, even with some changes to the core I have in mind. But it would be nice if we could measure the interval in minutes, not seconds!
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
User avatar
Norby
---- E L I T E ----
---- E L I T E ----
Posts: 2577
Joined: Mon May 20, 2013 9:53 pm
Location: Budapest, Hungary (Mainly Agricultural Democracy, TL10)
Contact:

Re: OXP Performance tips

Post by Norby »

I asked cag to optimize a modified oolite-cloaking-device-equipment.js what I find once in the forum This solve a problem that missiles should lost lock when the player cloaks. Here is the original:

Code: Select all

this.activated = function()
{
   player.ship.isCloaked = !player.ship.isCloaked;
  
   if (player.ship.isCloaked)
   {
     // find missiles targeting player
      var missilesTargetingPlayer = system.filteredEntities(this, function(ent)
                                    {
                                       return (ent.isMissile && ent.target == player.ship);
                                    },   player.ship, 25600);
      for (var i=0;i<missilesTargetingPlayer.length;i++)
      {
          missilesTargetingPlayer[i].target = null; // target lost -> missile detonates
      }
   }
}
The optimized variant with cag's comments:
cag wrote:
I replaced filteredEntities with entitiesWithScanClass as it runs 8x's faster. It's too bad it doesn't take a function to filter entities, but whenever the situation can be defined by class, it's great! There's also a countEntitiesWithScanClass if you only need to know how many (really fast and no array to worry about). And matching pairs for role & primary role.

With no missiles, this has an overhead cost of 0.093 ms (vs. 0.138 ms for your version); on a population of 54, it profiled at 0.168 (vs. 0.847 ms for your version). Code:

Code: Select all

this.activated = function activated()
    {
        "use strict";
        var that = activated;
        var entitiesWithScanClass = (that.entitiesWithScanClass = that.entitiesWithScanClass
                                                                     || system.entitiesWithScanClass);
        
        var isCloaked, ps = player && player.ship;
        isCloaked = ps.isCloaked = !ps.isCloaked;
      
        if (isCloaked)
        {
            // find missiles targeting player
            var ent, scannerRange = ps.scannerRange;
            var missilesTargetingPlayer = entitiesWithScanClass( 'CLASS_MISSILE', ps, scannerRange );
            for( var i = 0, len = missilesTargetingPlayer.length; i < len; i++ ) 
            {
                ent = missilesTargetingPlayer[i];
                if( !ent.isValid ) continue;
                if( ent.target !== ps ) continue;
                ent.target = null;     // target lost -> missile detonates
            }
        }
    }
I tested it using the next commands in debug console:

Code: Select all

PS.awardEquipment("EQ_CLOAKING_DEVICE");
PS.target.target=PS; PS.target.fireMissile();
Before these I put my target lock to a ship which has missiles and paused the game. After these I unpaused and cloaked, then the missile exploded immediately so the code is working well. I think this should be in the core.
Last edited by Norby on Mon Oct 16, 2017 7:14 am, edited 2 times in total.
User avatar
Norby
---- E L I T E ----
---- E L I T E ----
Posts: 2577
Joined: Mon May 20, 2013 9:53 pm
Location: Budapest, Hungary (Mainly Agricultural Democracy, TL10)
Contact:

Re: OXP Performance tips

Post by Norby »

@cag: I installed the full pack of your optimized oolite-*.js files and I got some errors. First occur with cim's Comms Pack:

Code: Select all

11:42:02.507 [script.javaScript.exception.unexpectedType] ReportJSError (OOJavaScriptEngine.m:203): ***** JavaScript exception (Comms Pack A 0.5): TypeError: obj[role][personality] is undefined
11:42:02.507 [script.javaScript.exception.unexpectedType] ReportJSError (OOJavaScriptEngine.m:214):       /home/norbi/.Oolite/AddOns/Test.oxp/Scripts/oolite-priorityai.js, line 7197.
Next is with Snoopers:

Code: Select all

11:42:11.037 [script.javaScript.exception.unexpectedType] ReportJSError (OOJavaScriptEngine.m:203): ***** JavaScript exception (snoopers 2.5): TypeError: strA is undefined
11:42:11.037 [script.javaScript.exception.unexpectedType] ReportJSError (OOJavaScriptEngine.m:214):       /home/norbi/GNUstep/Library/ApplicationSupport/Oolite/ManagedAddOns/oolite.oxp.Svengali.CCL.oxz/Scripts/Cabal_Common_Functions.js, line 256.
11:42:11.040 [LogEvents] GlobalLog (OOJSGlobal.m:256): Player gui screen changed from GUI_SCREEN_LOAD to GUI_SCREEN_STATUS
11:42:11.097 [script.javaScript.exception.unexpectedType] ReportJSError (OOJavaScriptEngine.m:203): ***** JavaScript exception (snoopers 2.5): TypeError: strA is undefined
11:42:11.097 [script.javaScript.exception.unexpectedType] ReportJSError (OOJavaScriptEngine.m:214):       /home/norbi/GNUstep/Library/ApplicationSupport/Oolite/ManagedAddOns/oolite.oxp.Svengali.CCL.oxz/Scripts/Cabal_Common_Functions.js, line 256.
Finally when I selected the F4/Primable Equipment the game crashed. The end of the log show the next lines about 20 thousand times within a second:

Code: Select all

11:49:54.956 [script.javaScript.stackTrace] OOJSDumpStack (OOJavaScriptEngine.m:811):  0 (oolite-primable-equipment-manager.js:228) _configurePrimableEquipment()
11:49:54.956 [script.javaScript.stackTrace] DumpVariable (OOJavaScriptEngine.m:731):     this: [Script "oolite-primable-equipment-register" version (nil)]
11:49:54.956 [script.javaScript.error.cantConvertTo] ReportJSError (OOJavaScriptEngine.m:203): ***** JavaScript error (snoopers 2.5): can't convert stage1 to string
11:49:54.956 [script.javaScript.error.cantConvertTo] ReportJSError (OOJavaScriptEngine.m:214):       /home/norbi/.Oolite/AddOns/Test.oxp/Scripts/oolite-primable-equipment-manager.js, line 228.
11:49:54.956 [script.javaScript.stackTrace] OOJSDumpStack (OOJavaScriptEngine.m:811):  0 (oolite-primable-equipment-manager.js:228) _configurePrimableEquipment()
11:49:54.956 [script.javaScript.stackTrace] DumpVariable (OOJavaScriptEngine.m:731):     this: [Script "oolite-primable-equipment-register" version (nil)]
11:49:54.956 [script.javaScript.error.cantConvertTo] ReportJSError (OOJavaScriptEngine.m:203): ***** JavaScript error (snoopers 2.5): can't convert stage1 to string
11:49:54.956 [script.javaScript.error.cantConvertTo] ReportJSError (OOJavaScriptEngine.m:214):       /home/norbi/.Oolite/AddOns/Test.oxp/Scripts/oolite-primable-equipment-manager.js, line 228.
11:49:54.956 [script.javaScript.stackTrace] OOJSDumpStack (OOJavaScriptEngine.m:811):  0 (oolite-primable-equipment-manager.js:228) _configurePrimableEquipment()
11:49:54.956 [script.javaScript.stackTrace] DumpVariable (OOJavaScriptEngine.m:731):     this: [Script "oolite-primable-equipment-register" version (nil)]
11:49:54.956 [script.javaScript.error.cantConvertTo] ReportJSError (OOJavaScriptEngine.m:203): ***** JavaScript error (snoopers 2.5): can't convert stage1 to string
11:49:54.956 [script.javaScript.error.cantConvertTo] ReportJSError (OOJavaScriptEngine.m:214):       /home/norbi/.Oolite/AddOns/Test.oxp/Scripts/oolite-primable-equipment-manager.js, line 228.
Your v2 pack give the same results.
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

Norby wrote: Fri Oct 06, 2017 10:59 am
@cag: I installed the full pack of your optimized oolite-*.js files and I got some errors. First occur with cim's Comms Pack:
Fixed. Stupid mistake, not setting re-used array's length = 0
Norby wrote: Fri Oct 06, 2017 10:59 am
Next is with Snoopers:
I cannot reproduce this. Try my new set and if it recurs, please provide a list of oxp's your using.
Norby wrote: Fri Oct 06, 2017 10:59 am
Finally when I selected the F4/Primable Equipment the game crashed. The end of the log show the next lines about 20 thousand times within a second:
Fixed, I hope. I fixed *a* bug in Primable Equipment, but sure it's the same!

The new set can be found here:

https://www.dropbox.com/s/cudwuq8d56epc ... s.zip?dl=0

Keep those bugs coming!
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

Norby wrote: Fri Oct 06, 2017 10:44 am
This solve a problem that missiles should lost lock when the player cloaks. ...
... the missile exploded immediately so the code is working well. I think this should be in the core.
Forgot to mention that I included the new oolite-cloaking-device-equipment.js code in my new set. Now everyone can play around with it.

Edit: Restored to original. Link unchanged.
Last edited by cag on Sat Oct 07, 2017 8:27 pm, edited 1 time in total.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

Norby wrote: Fri Oct 06, 2017 10:59 am
Next is with Snoopers:

Code: Select all

11:42:11.037 [script.javaScript.exception.unexpectedType] ReportJSError (OOJavaScriptEngine.m:203): ***** JavaScript exception (snoopers 2.5): TypeError: strA is undefined
...
I did some digging and it turns out that this in not a bug of mine (whew!). Snoopers' event handler for guiScreenChanged checks the version #'s of some oxp's. Among those are oolite-constrictor-hunt and oolite-nova and it's testing for version 1.77, using Svengali's CCL. The problem is that none of the Resouces\Scripts files have a version number.

Code: Select all

this.guiScreenChanged = function()
{
	if(this.snoopersInit){
		var requires = ['buoyRepair','1.3.2','AsteroidStorm','4.03','oolite-constrictor-hunt','1.77','oolite-nova','1.77','PlanetFall','1.51'];
		var checked = this.helper.oxpVersionTest2Array(this.name,requires,1);
...
oxpVersionTest2Array in turn calls strCompareVersion

Code: Select all

        check = this.strCompareVersion(worldScripts[requires[i]].version,requires[i+1]);
assuming the script has a .version property. So the 1st parm, strA, is undefined.

I can't reproduce this bug. I think the reason is that I'm overwriting the originals in the Resouces\Scripts folder, where they inherit a version number from oolite itself. worldScripts['oolite-constrictor-hunt'].version shows up as 1.85 in my debug console. So rather than testing these files in your AddOns/Test.oxp, try putting them in the Resouces\Scripts folder (backing up first, of course :P ) and see if this problem goes away.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6552
Joined: Wed Feb 28, 2007 7:54 am

Re: OXP Performance tips

Post by another_commander »

Apologies for maybe going a bit off-topic, but the missiles detonating when player cloaks script should not be a part of this testing. The reason we specifically do not want missiles detonating is because the cloaking device should not be used as a better-than-ECM ECM. What we have now is that the missile simply loses track of the target when it cloaks and re-aquires it within seconds once the target de-cloaks. When not tracking the target due to cloaking, it simply flies on a more or less straight line.

This is not meant to start up a discussion about the cloaking device, just to point out the issue because it can certainly affect testing. If someone would like to discuss the rights or wrongs of the above, I would recommend opening a different topic in Discussion.
User avatar
Norby
---- E L I T E ----
---- E L I T E ----
Posts: 2577
Joined: Mon May 20, 2013 9:53 pm
Location: Budapest, Hungary (Mainly Agricultural Democracy, TL10)
Contact:

Re: OXP Performance tips

Post by Norby »

Thanks cag, your latest pack fixed the problems except the strA one, which is indeed caused by worldScripts['oolite-constrictor-hunt'].version returns nothing at me (and oolite-nova also). The result is the same if I put the scripts into the core resources folder.

About missiles I think my script is an old one, originated from before when the core got the shiny js AI which do better job than this fix. Sorry for the extra work, at least your code is a good example how to optimize a similar js.
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

@ Norby
Norby wrote: Sat Oct 07, 2017 1:25 pm
... at least your code is a good example how to optimize a similar js.
Not really, I rushed it (not enough coffee, no excuse). We cannot re-use the missilesTargetingPlayer array because it gets clobbered by the return from entitiesWithScanClass. It's declaration should just be

Code: Select all

var missilesTargetingPlayer;
and remove the line

Code: Select all

missilesTargetingPlayer.length = 0;
---------------------------------------------------------------------------------------------------------------------

Here's one for the wiki. It turns out that .heading and .vectorForward are always identical (verified in the source code). And they are always unit vectors, so there's no need to use .direction() on them directly. If your callback uses both, just pick one. .heading is easier to type but .vectorForward is nearer (in Ship vs Entity) and that's one less get in your profile.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
Astrobe
---- E L I T E ----
---- E L I T E ----
Posts: 609
Joined: Sun Jul 21, 2013 12:26 pm

Re: OXP Performance tips

Post by Astrobe »

cag wrote: Mon Oct 16, 2017 6:51 am
Here's one for the wiki. It turns out that .heading and .vectorForward are always identical (verified in the source code). And they are always unit vectors, so there's no need to use .direction() on them directly. If your callback uses both, just pick one. .heading is easier to type but .vectorForward is nearer (in Ship vs Entity) and that's one less get in your profile.
Added. Together with your first post about GC techniques. And, yes, I've just discovered <pre></pre> blocks...
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

In frame callbacks (and Timers to a lesser degree), we need to reduce the amount the garbage collection (GC) has to deal with. Whether it's 50 or 200 oxp's loading, every little bit helps. My edits to the
Resouces\scripts files were meant more for demonstration, not performance, as these files are on all machines. I was pleasantly surprised by the savings. Have you noticed a new line in your logs (I forgot one log() statement):

Code: Select all

[oolite-libPriorityAI]: ship died, __handler_reuse = 6, __comms_reuse = 0
Each object that's reused is one less for GC. The record to date for a ship is __handler_reuse = 114, __comms_reuse = 89!

I've ported some of the core vector & quaternion functions into JScript. This was made easier by the fact that both Vector3D & Quaternion object's properties can be accessed as if they were arrays (eg. position.x == position[0]).
This is about 5% slower but by keeping these property gets to a minimum, it's negligible. And I can use a single pool, vs. 1 for each object type.

Care must be taken when interfacing with oolite's objects. You access by:

Code: Select all

copy_vector( ent.position, my_var ); // my_var is an array
This will be twice as slow as a regular assignment ('my_var = ps.position;'), as your doing 3 property gets instead of just one; both generate the same amount of garbage.

When you assign to an oolite object, NEVER use copy_vector or copy_quaternion! Always do it as normal:

Code: Select all

ent.orientation = my_var;
This is the fastest and generates no garbage. The copy functions would create garbage (JS constructs a working object to interface with the core) and is 2 x's slower (3 x's if both are oolite objects, you'd be doing 6/8 JSObjectGetVector calls!)

You can DL the file here: https://www.dropbox.com/s/sojm6ulor13s0 ... s.zip?dl=0

You're probably wondering, what's this guy been smoking? 8) Why bother? Well, if all your calculations are done using local arrays (which, of course, you re-use), you'll generate NO garbage (except when interfacing, which you cannot control). This will cost you a little time; your vector calculations will be about 15% slower, off-set somewhat by reduced property gets.

I profiled some code I do every frame: 4 .add(), 1 .subtract(), 4 .multiply() & 1 .direction(). It was slower by 0.015 ms! A small price to greatly reduce garbage. By doing vector work locally, I was able to reduce garbage in a 5 minute period from 60 MB to 37 MB, changing the frequency of GC from every 1m 44s to 2m 55s, with no significant drop in frame rate. For perspective, the current version that's been around for 3 years generates almost 180 MB every 5 minutes, so GC happens every 38s.

Here are a few examples:

Code: Select all

//  target_vector = ps_target.position.subtract( ps ).direction();
    copy_vector( ps_target.position, vector );
    subtract_vectors( vector, ps_position, target_vector );
    unit_vector( target_vector, target_vector );
    
//  effect_posn = ps_position.add( ps_vectorForward.multiply( 50 + viewPosition.z ) );
    scale_vector( ps_vectorForward, (50 + viewPosition[2]), vector );
    add_vectors( vector, ps_position, effect_posn );

//  effect_posn = effect_posn.add( ps_vectorRight.multiply( viewPosition.x ) );
    scale_vector( ps_vectorRight, viewPosition[0], vector );
    add_vectors( vector, effect_posn, effect_posn );
Just remember, this is the LAST step in optimizing your oxp. Write, debug, test, profile speed, then take out the garbage.

Full disclosure: there sometimes is a loss of accuracy

There are a few situations where the accuracy of the JS calculations is less than optimal. Included in the download are my test functions and in 2 cases I had to reduce the precision from 1E-10 to 1E-6.

The first case is generating vectorForward, vectorRight & vectorUp locally, from ps.orientation. This has a lot of floating point calculations using values near zero & one and accuracy is a known issue. The core uses higher precision floats and this explains the disparity. That said, it hasn't impacted me to date, though I do round distance to meters.

The second deals with rotations; rotating the ship 180 degrees in one axis in the core (20 steps of 9 degees), repeating in a different axis locally and comparing the angles traversed. The angles, in radians, agree to 6 decimal places, good enough for what I'm doing. But I can imagine a (large) ship scraping the dock's bulkheads if its docking calculations are as imprecise! :oops:

The latest version of ReverseControl uses it, if you want to take it for a spin.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

the new Resources\Scripts has been updated (link is unchanged)
https://www.dropbox.com/s/cudwuq8d56epc ... s.zip?dl=0

Just a couple minor bug fixes; the reason for this post is I rewrote the _paddingText() function in oolite-contracts-helpers.js
By using a few different sizes of whitespace characters, the padding string has shrunk from hundreds (over 600 in one case!) to a couple dozen, worst case.
I doubt this will impact speed or garbage but debugging mission screens with variables that size was really annoying.

In doing so, I discovered that our defaultFont doesn't support the full range of whitespace characters that JS regular expressions do. (It instead outputs question marks, which makes for a messy screen!) So I have a question for non-Windows authors: is defaultFont consistent over different OS's? I would think so, but you never know.

I tested by running a mission screen with a message with

Code: Select all

'2000' + String.fromCharCode( 0x2000 ) + '2001' + String.fromCharCode( 0x2001 ) + ...
and noted which got '?'s. But it can be more easily tested by pasting this into the debug console while in space

Code: Select all

(function() {
    var fromCC = String.fromCharCode;
    var width = defaultFont.measureString
    player.consoleMessage( '200a ' + fromCC( 0x200a ) + width( fromCC( 0x200a ) ).toFixed(3) 
                         + ' 2004' + fromCC( 0x2004 ) + width( fromCC( 0x2004 ) ).toFixed(3)
                         + ' 2005' + fromCC( 0x2005 ) + width( fromCC( 0x2005 ) ).toFixed(3) 
                         + ' 2006' + fromCC( 0x2006 ) + width( fromCC( 0x2006 ) ).toFixed(3), 10 )
    player.consoleMessage( '2008 ' + fromCC( 0x2008 ) + width( fromCC( 0x2008 ) ).toFixed(3) 
                         + ' 2009' + fromCC( 0x2009 ) + width( fromCC( 0x2009 ) ).toFixed(3)
                         + ' 3000' + fromCC( 0x3000 ) + width( fromCC( 0x3000 ) ).toFixed(3) 
                         + ' 1680' + fromCC( 0x1680 ) + width( fromCC( 0x1680 ) ).toFixed(3), 10 )
    player.consoleMessage( '180e ' + fromCC( 0x180e ) + width( fromCC( 0x180e ) ).toFixed(3) 
                         + ' 2000' + fromCC( 0x2000 ) + width( fromCC( 0x2000 ) ).toFixed(3)
                         + ' 2001' + fromCC( 0x2001 ) + width( fromCC( 0x2001 ) ).toFixed(3) 
                         + ' 2002' + fromCC( 0x2002 ) + width( fromCC( 0x2002 ) ).toFixed(3), 10 )
    player.consoleMessage( '2003 ' + fromCC( 0x2003 ) + width( fromCC( 0x2003 ) ).toFixed(3) 
                         + ' 2007' + fromCC( 0x2007 ) + width( fromCC( 0x2007 ) ).toFixed(3)
                         + ' 202f' + fromCC( 0x202f ) + width( fromCC( 0x202f ) ).toFixed(3) 
                         + ' 205f' + fromCC( 0x205f ) + width( fromCC( 0x205f ) ).toFixed(3), 10 )
})()
and pausing the game. Here are my results to compare:

Code: Select all

 name         unicode     width   defaultFont
 ====         ========    =====   ===========
 HAIR         0x200a      0.0125  yes
 THREE-PER-EM 0x2004      0.217   yes
 FOUR-PER-EM  0x2005      0.217   yes 
 SIX-PER-EM   0x2006      0.217   yes
 PUNCTUATION  0x2008      0.217   yes  
 THIN         0x2009      0.217   yes
 IDEOGRAPHIC  0x3000      0.217   yes 
 OGHAM        0x1680      0.477   no
 MONGOLIAN    0x180e      0.477   no
 EN QUAD      0x2000      0.477   no
 EM QUAD      0x2001      0.477   no
 EN SPACE     0x2002      0.477   yes
 EM SPACE     0x2003      0.477   yes
 FIGURE       0x2007      0.477   no
 NARROW       0x202f      0.477   no
 MEDIUM       0x205f      0.477   no
defaultFont supports 9 of the 16 but only 3 unique lengths.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 197
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

cag wrote: Sat Oct 21, 2017 1:41 am
I've ported some of the core vector & quaternion functions into JScript.
...
You can DL the file here: https://www.dropbox.com/s/sojm6ulor13s0 ... s.zip?dl=0
I've updated vector_fns to add 2 variants on 'angle_between', namely '_angle_between_unitV' and '_angle_between_two_unitV'. [DL link is the same]

Often one or both vectors are already unit vectors, either in your code or as referenced in oolite (eg. vectorForward). Using these variants eliminates redundant calculations, as 'angle_between' always converts both vectors to unit vectors. '_angle_between_unitV' assumes the 1st parm is a unit vector.

In Telescope 2.0, I no longer use 'angle_between' at all but have 6 calls to '_angle_between_unitV' and 11 to '_angle_between_two_unitV'!
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
Post Reply