0

General Questions

Hi, I tried to move logic to a process using setTimeout in order to show a loading screen right away and update the page once the process is complete.

Sometimes it kind of works, sometimes it does not. Now I think I need to know some more theory to get to the bottom of this problem.

Is there always a programming error involved when a website blocks the whole browser (Firefox) or everything in a tab (Chrome)?

Can it be a case of leaky abstraction (like a C/C++ game loop that never sleeps and does not allow the CPU to work on other processes)? Do I have to write a mechanism that works with several setTimeouts if the process takes long and has a lot of work to do?

Anything else I need to look into / keep in mind?

Details

I have read https://softwareengineering.stackexchange.com/questions/202047/what-determines-which-javascript-functions-are-blocking-vs-non-blocking and it does not seem to answer the questions.

The problem can be seen here: http://procgames.com/raidaces/

Firefox sometimes is blocked and does not update anything until the whole process is complete (weird, because I render the loading state in the canvas before I call the loading process).

In Chrome the text is blocked (can not be selected) until the process is complete, so I assume the whole Tab is frozen.

What happens is: 25 images (1024x1024) are rendered in canvases and moved to img entities using toDataURL (so the client is pretty busy).

Maybe I am doing something wrong? The program starts in the HTML file:

        <div class=container>
        <h1>Raid Aces</h1>

        <canvas id="gameCanvas" width="800" height="600" onClick="toggleRunning(gameContext)"></canvas>
        <div id="debugArea">The Epic Battles Of The Ancient Clans ... or whatever ...</div>
        <canvas id="mapCanvas" width="128" height="128"></canvas>

        <div class="clear"></div>
        <div id="hiddenContentArea"></div>

        <script>

        var gameCanvas = document.getElementById('gameCanvas');
        var gameContext = gameCanvas.getContext('2d');
        var hiddenContentArea = document.getElementById('hiddenContentArea');

        var mapCanvas = document.getElementById('mapCanvas');
        var mapContext = mapCanvas.getContext('2d');

        gameContext.beginPath();
        gameContext.rect(0, 0, 800, 600);
        gameContext.fillStyle = "#88BB88";
        gameContext.fill();
        gameContext.font = "bold 16px sans-serif";
        gameContext.fillStyle = "#FFFFFF";
        gameContext.fillText("Loading", 350, 294);

        mapContext.beginPath();
        mapContext.rect(0, 0, 128, 128);
        mapContext.fillStyle = "#88BB88";
        mapContext.fill();
        mapContext.font = "bold 10px sans-serif";
        mapContext.fillStyle = "#FFFFFF";
        mapContext.fillText("Loading", 42, 62);

        initEngine(gameContext);
        initGame(gameContext, hiddenContentArea);

        document.addEventListener('keydown', function(event) {
            handleInputEvent(event, gameContext);
        });
        </script>
    </div>

which calls initGame ...

function initGame(gameContext, hiddenContentArea) {

    if (!engine.isInitialized) {
        throw "error_engine_not_initialized";
    }

    var eventsModule = engine.getModule("events");
    log("Rendering Map, please be patient!");
    eventsModule.changeGameState(new GameStateLoading(engine));
}

... and this is where the asynchronous call should be realized:

ModuleEvents.prototype.changeGameState = function(moduleGameState) {

    var oldGameState = this.gameState;
    if (oldGameState) {
        oldGameState.uninit();
    }

    this.gameState = moduleGameState;
    var callbackFunction = this.makeCallbackExecution(oldGameState,
            this.gameState, this.eventBus);

    setTimeout(callbackFunction, 10);
};

ModuleEvents.prototype.makeCallbackExecution = function(oldGameState,
        newGameState, eventBus) {

    return function() {
        newGameState.init();
        eventBus
                .fireEvent(new EventGameStateChanged(oldGameState, newGameState));
    };
};
5
  • Can you use webworkers? Commented May 4, 2014 at 19:06
  • Have not heard of those until now. I will look into them, but I guess I want to at least understand the "hacks" that seem to work for most projects so far. It seems like it should be possible to create a site that is responsive, even during the loading / asset creation process. Commented May 4, 2014 at 19:25
  • I haven't looked deep enough into your project because it includes a lot of scripts, but are you sure that your callbackFunction (which you obtain via this.makeCallbackExecution(oldGameState, this.gameState, this.eventBus);) works fast? Because if it isn't then there is no sense (almost) in using setTimeout: if function executes for n seconds, then UI will be "unresponsive" during these n seconds. So it's important to split jobs in chunks (which isn't easy, by the way). Commented May 4, 2014 at 20:24
  • I know there is only a single thread for the tab, but I assumed that there has to be some kind of simulated concurrency when using setTimeout. If that is not the case, then your comments are pretty much the right answer to the question. The funtion does a lot. It is spawned 25 times, but each time it builds one whole image. So I'd have to "sleep" more often, for example 10ms after each tile row of an image has been rendered? Do you know of any resources that confirm that? Guess I can live with it because the image creation will probably eventually be moved to the server. Commented May 4, 2014 at 21:03
  • I think, reducing of "queue length", that is, number of fired timers, to 2-3 would have positive impact on UI responsiveness and performance. But I can't name any resource to confirm my words. It's just my thoughts (which may turn out to be wrong) and nothing more. Commented May 4, 2014 at 21:20

1 Answer 1

1

I've looked at your project using Chrome Developer Tools (CDT) Timeline option and noticed the following:

CDT Timeline view

I'm not a CDT expert but it seems like these 25 timers are set simultaneously (in a loop at gamestate_loading.js:109) and are going to fire at the same time. Now imagine you're a thread serving a web page. Your duties is to draw UI (maybe in response to user) and execute JS. But suddenly here comes 25 JS tasks as well as usual GUI requests. I don't know details of timers scheduling, but I don't think that firing plenty of them at the same time is a good idea.

And yes, these 25 timers will be executed serially, one after another because of javascript's single thread nature.

I'd recommend you to investigate applicability of WebWorkers to your case. They're designed in order to separate heavy computations from UI. Also you may want to maximally optimize work you're doing in timers' callbacks (minimize DOM and rendering operations, for example).

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot for your time. I spaced out the timing by using the index and it is a lot more responsive, even if it is still a little bumpy. Makes sense to say the least ... I guess one could even say setting the timers to start at the same time was stupid embarrassed. I'll definitely check out WebWorkers. I would +1 if I had the reputation, btw.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.