Is there a way to minify Screeps files?



  • I have tried to use a standard JS minifyer but it does not recognise the filter => from lodashJS. Has anyone been able to do this. It would really help my CPU use. Thanks!


  • Culture

    I've had no problem minifying with Uglify, but I also haven't seem much benefit from doing it so I stopped.



  • As a noob I'm just guessing here, but I don't think the CPU cost is based on the number of lines or characters of code.  I think instead it is based on the number of function calls you make and for which functions you call (functions have different costs roughly based on how much real CPU work they do).

    I think this is true because I've seen my CPU cost fluctuate based on the frequency at which I run a certain module.  I have corrected the issue by adjusting the frequency using a little modulus math:

    if (Game.time % 25 != 0) return;

    ...not everything needs to run at every tick.

    Hope that helps.


  • Culture

    bones1999 - 

    When you "require" a file it has to get parsed. The more characters it has the longer, in theory, it will take to parse it (even if those characters are whitespace). The "require" statement does cache the results, so you do not pay that parsing cost each tick. However, every time the global object resets or you upload your code there will be a CPU usage spike as that cache will be empty. In that case it may be beneficial to minify the code.



  • tedivm,


    I found this article, which explained enough for me to have a good idea as to how it works: http://support.screeps.com/hc/en-us/articles/204332302-How-does-CPU-limit-work


    What they really care about is how long it takes for all your scrips to execute per "tick," or player turn.  They must capture the current time before starting your scripts (calling module.exports.loop), then capture the time after it completes, calculate the delta, etc.  Your CPU limit explicitly defines the number of milliseconds they will allow your scripts to run.  


    The good news is that if you underutilize the time provisioned to you, then the excess gets carried over into the next tick (up to 10 seconds worth), which will allow for the occasional spikes.  Though it's also worth noting that there is a 500 ms maximum per tick.


    I don't think load or "compile" time is factored into play here, but even if it was a few spikes here and there aren't an issue.


     


  • Culture

    bones1999-

    I've read that article, yes. I can say beyond a doubt that you are mistaken on this one, compile time is counted against you. When you call "require" it happens in your tick- they don't run "require" for you to precache your code (and that wouldn't make sense to do for a number of reasons). So just like any other call, "require" takes time and that time counts as CPU during your turn.

    Before commenting again just test this out.

    var start = Game.cpu.getUsed()

    require('some_module')

    console.log(Game.cpu.getUsed()-start)

     

    The larger the module you require the longer that benchmark will take. 



  • Hey tedivm,

     

    I did some testing and I can confirm that the script size is not something anyone should actually worry about.  First, I'll say that after looking at your code I knew if you ran that and saw a difference in what is stored in Game.cpu then yes, you're right in that it counts against your CPU (I didn't run the code myself but I trust in what you reported).

    That said, I also know that "compiling" the code does not take a lot of real CPU time and shouldn't be a concern when playing this game.  To prove that, here is what I did:

    1. Created a script named "bigscript" which has 30K+ lines of code:

    <code>

    var bigscript = function () {

    var _run = function () {
    for (var roomName in Game.rooms) {
    var room = Game.rooms[roomName];
    var spawns = room.find(FIND_MY_SPAWNS);
    var creeps = room.find(FIND_MY_CREEPS);

    var upgraderCount = creeps.filter(function (c) {
    return c.memory.role == 'upgrader';
    }).length;

    var harvesterCount = creeps.filter(function (c) {
    return c.memory.role == 'harvester';
    }).length;

    var builderCount = creeps.filter(function (c) {
    return c.memory.role == 'builder';
    }).length;

    var energizerCount = creeps.filter(function (c) {
    return c.memory.role == 'energizer';
    }).length;
    }
    };

    var _run = function () {
    for (var roomName in Game.rooms) {
    var room = Game.rooms[roomName];
    var spawns = room.find(FIND_MY_SPAWNS);
    var creeps = room.find(FIND_MY_CREEPS);

    var upgraderCount = creeps.filter(function (c) {
    return c.memory.role == 'upgrader';
    }).length;

    var harvesterCount = creeps.filter(function (c) {
    return c.memory.role == 'harvester';
    }).length;

    var builderCount = creeps.filter(function (c) {
    return c.memory.role == 'builder';
    }).length;

    var energizerCount = creeps.filter(function (c) {
    return c.memory.role == 'energizer';
    }).length;
    }
    };

    // KEEP REPEATING THE DEFINITION OF _run() UNTIL YOU HAVE 30K+ LINES OF CODE

    return {
    run: _run
    };

    };

    module.exports = bigscript();

    </code>

     

    2. Then in the main script:

    <code>

    var bigscript = require('bigscript');

    module.exports.loop = function () { };

    </code>

     

    If you do this, you'll notice that for the first two ticks you'll see CPU utilization (the graphic in the upper-right part of the screen) completely max out.  That proves you are right about it affecting CPU time.  However, after it uploads and a couple ticks pass, CPU utilization returns to normal and is then only affected by what code is actually executed per tick-- proving that the modules are cached (as you mentioned), which is how 'require' natively works (ie, even outside of this game).  The CPU spike only occurs when submitting new changes.  

    A momentary spike is not something to warrant a change to your workflow, toolchain, etc.  Also remember that the spike you observe by following the test I produced is with a script that has 30K+ lines of code-- something that I can't imagine would be achieved through the normal use of this application/game.

    Cheers,

    Bones

     



  • ...one more thing I'll add is that I tested within a private server environment I'm hosting, so I'm not confined to any CPU limit.  I don't know what would happen if you ran my test in the official world instance.  And the fact that my CPU utilization maxed out may also be partly due to the fact that the server I'm renting from GoDaddy is not the best (only two cores provisioned to my VM).


  • Culture

    Yup, you're literally discovering what I've been saying. When `require` doesn't have the object cached it'll have to parse the files, and the larger the files the longer that takes. However, `require` does cache this between ticks so it doesn't happen every time.

    There are some big consequences of this-

    * At lower levels it's possible to deplete your bucker to the point where you can't load your code. If you have a bucket of 0, it takes 50cpu to parse your code, and your CGL limit is 40 cpu, then your code will not run and will continuously time out. You can resolve this by putting a bucket check before your first require, so if you don't have enough bucket it stops running until you do.

    * Clusters of Global Resets will mean that this extra CPU gets used a lot in clusters. The admins have mostly resolved this by giving us extra bucket when they trigger a global reset storm, but sometimes the reset storms happen for too long and that system stops working. When that happens you pay for the parsing cost a lot more regularly.

    As an additional note, there's a limit of 2mb for user code. So if someone is taking a large library (like a newer version of lodash than is provided) they may want to minify that simply to give themselves more space for their own code.



  • Script is not only loaded when you save changes : it is reloaded randomly, then your scope is reset. You can do a "console.log" outside the returned module to see when it happens. I wonder if something like this could be benefical :

    main.js

    console.log("scope resetted")
    const core = new require("core")();

    exports.loop = function(){
    return core.loop()
    }