How should I use requestAnimationFrame and setTimeout in parallel to create a better game loop?

My goal is to create an effective game loop that uses requestAnimationFrameto update the display canvas and setTimeoutto update the game logic. My question is, should I put all the drawing operations inside the loop, requestAnimationFrameor only the main drawing operation that updates the html canvas?

What I mean by "all drawing operations" is all buffering. For example, I will draw all the sprites in a buffer, and then draw a buffer on the main canvas. On the one hand, if I put all the buffering in requestAnimationFrame, I will not waste time calculating cpu for each update of the logic, on the other hand, the drawing will be heavy cpu and may make it requestAniomationFramewait until all these operations are completed ... The split point of logical updates from the drawing is such that it is requestAnimationFramenot linked to processing without drawing.

Does anyone have any experience with this approach to creating a game loop? And don't say “just put it all in, requestAnimationFrame” because it slows down the rendering. I am convinced that separating logic from drawing is the way to go. Here is an example of what I am saying:

/* The drawing loop. */
function render(time_stamp_){//First parameter of RAF callback is timestamp.
    window.requestAnimationFrame(render);

    /* Draw all my sprites in the render function? */
    /* Or should I move this to the logic loop? */
    for (var i=sprites.length-1;i>-1;i--){
        sprites[i].drawTo(buffer);
    }

    /* Update the on screen canvas. */
    display.drawImage(buffer.canvas,0,0,100,100,0,0,100,100);
}

/* The logic loop. */
function update(){
    window.setTimeout(update,20);

    /* Update all my sprites. */
    for (var i=sprites.length-1;i>-1;i--){
        sprites[i].update();
    }
}

Thank!

Edit:

-, , , , script, DOM.

+4
2

, , JavaScript . , , . , , , requestAnimation Frame. , , . , .

var engine = {
        /* FUNCTIONS. */
        /* Starts the engine. */
        /* interval_ is the number of milliseconds to wait between updating the logic. */
        start : function(interval_) {
            /* The accumulated_time is how much time has passed between the last logic update and the most recent call to render. */
            var accumulated_time = interval_;
            /* The current time is the current time of the most recent call to render. */
            var current_time = undefined;
            /* The amount of time between the second most recent call to render and the most recent call to render. */
            var elapsed_time = undefined;
            /* You need a reference to this in order to keep track of timeout and requestAnimationFrame ids inside the loop. */
            var handle = this;
            /* The last time render was called, as in the time that the second most recent call to render was made. */
            var last_time = Date.now();

            /* Here are the functions to be looped. */
            /* They loop by setting up callbacks to themselves inside their own execution, thus creating a string of endless callbacks unless intentionally stopped. */
            /* Each function is defined and called immediately using those fancy parenthesis. This keeps the functions totally private. Any scope above them won't know they exist! */
            /* You want to call the logic function first so the drawing function will have something to work with. */
            (function logic() {
                /* Set up the next callback to logic to perpetuate the loop! */
                handle.timeout = window.setTimeout(logic, interval_);

                /* This is all pretty much just used to add onto the accumulated time since the last update. */
                current_time = Date.now();
                /* Really, I don't even need an elapsed time variable. I could just add the computation right onto accumulated time and save some allocation. */
                elapsed_time = current_time - last_time;
                last_time = current_time;

                accumulated_time += elapsed_time;

                /* Now you want to update once for every time interval_ can fit into accumulated_time. */
                while (accumulated_time >= interval_) {
                    /* Update the logic!!!!!!!!!!!!!!!! */
                    red_square.update();

                    accumulated_time -= interval_;
                }
            })();

            /* The reason for keeping the logic and drawing loops separate even though they're executing in the same thread asynchronously is because of the nature of timer based updates in an asynchronously updating environment. */
            /* You don't want to waste any time when it comes to updating; any "naps" taken by the processor should be at the very end of a cycle after everything has already been processed. */
            /* So, say your logic is wrapped in your RAF loop: it only going to run whenever RAF says it ready to draw. */
            /* If you want your logic to run as consistently as possible on a set interval, it best to keep it separate, because even if it has to wait for the RAF or input events to be processed, it still might naturally happen before or after those events, and we don't want to force it to occur at an earlier or later time if we don't have to. */
            /* Ultimately, keeping these separate will allow them to execute in a more efficient manner rather than waiting when they don't have to. */
            /* And since logic is way faster to update than drawing, drawing won't have to wait that long for updates to finish, should they happen before RAF. */

            /* time_stamp_ is an argument accepted by the callback function of RAF. It records a high resolution time stamp of when the function was first executed. */
            (function render(time_stamp_) {
                /* Set up the next callback to RAF to perpetuate the loop! */
                handle.animation_frame = window.requestAnimationFrame(render);

                /* You don't want to render if your accumulated time is greater than interval_. */
                /* This is dropping a frame when your refresh rate is faster than your logic can update. */
                /* But it dropped for a good reason. If interval > accumulated_time, then no new updates have occurred recently, so you'd just be redrawing the same old scene, anyway. */
                if (accumulated_time < interval_) {
                    buffer.clearRect(0, 0, buffer.canvas.width, buffer.canvas.height);

                    /* accumulated_time/interval_ is the time step. */
                    /* It should always be less than 1. */
                    red_square.draw(accumulated_time / interval_);

                    html.output.innerHTML = "Number of warps: " + red_square.number_of_warps;

                    /* Always do this last. */
                    /* This updates the actual display canvas. */
                    display.clearRect(0, 0, display.canvas.width, display.canvas.height);
                    display.drawImage(buffer.canvas, 0, 0, buffer.canvas.width, buffer.canvas.height, 0, 0, display.canvas.width, display.canvas.height);
                }
            })();
        },
        /* Stops the engine by killing the timeout and the RAF. */
        stop : function() {
            window.cancelAnimationFrame(this.animation_frame);
            window.clearTimeout(this.timeout);
            this.animation_frame = this.timeout = undefined;
        },
        /* VARIABLES. */
        animation_frame : undefined,
        timeout : undefined
    };

, , . red_square - . , github! userpoth.imtqy.com , , - , . - , , , , , , .

+2

,

  • , . , , , , .
  • , , . , , - , .

20 .

, , .

requestAnimationFrame , 1/20 , .

+1

Source: https://habr.com/ru/post/1584567/


All Articles