简体   繁体   中英

My javascript canvas map script and poor performance

Basically below is my script for a prototype which uses 128x128 tiles to draw a map on a canvas which user can drag to move around.

Script does work. However I have a few problems to be solved: 1. Poor performance and I can't figure out why. 2. I am missing a method to buffer the tiles before the actual drawing. 3. If you notice any other issues also that could help me to make things run more smoothly it would be fantastic.

Some explanations for the script:

variables

coordinates - Defines the actual images to be displayed. Image file names are type of '0_1.jpg', where 0 is Y and 1 is X.

mouse_position - As name says, is keeping record of mouse position.

position - This is a poorly named variable. It defines the position of the context drawn on canvas. This changes when user drags the view.


Any assistance would be appreciated greatly. Thank you.

var coordinates = [0, 0];
var mouse_position = [0, 0];
var position = [-128, -128];

var canvas = document.getElementById('map_canvas');
var context = canvas.getContext('2d');

var buffer = [];
var buffer_x = Math.floor(window.innerWidth/128)+4;
var buffer_y = Math.floor(window.innerHeight/128)+4;


var animation_frame_request = function() {
    var a = window.requestAnimationFrame;
    var b = window.webkitRequestAnimationFrame;
    var c = window.mozRequestAnimationFrame;
    var d = function(callback) {
        window.setTimeout(callback, 1000/60);
    }

    return a || b || c || d;
}





var resizeCanvas = function() {
    window.canvas.width = window.innerWidth;
    window.canvas.height = window.innerHeight;


    window.buffer_x = Math.floor(window.innerWidth/128)+4;
    window.buffer_y = Math.floor(window.innerHeight/128)+4;


    window.buffer = [];

    for (row = 0; row < window.buffer_y; row++) {
        x = [];
        for (col = 0; col < window.buffer_x; col++) {
            x.push(new Image());
        }

        window.buffer.push(x);
    }
}




var render = function() {
    animation_frame_request(render);

    for (row = 0; row < window.buffer_y; row++) {
        for (col = 0; col < window.buffer_x; col++) {
            cy = window.coordinates[1]+row;
            cx = window.coordinates[0]+col;
            window.buffer[row][col].src =  'map/'+cy+'_'+cx+'.jpg';
        }
    }

    for (row = 0; row < window.buffer_y; row++) {
        for (col = 0; col < window.buffer_x; col++) {
            window.context.drawImage(window.buffer[row][col],
                                     window.position[0]+col*128,
                                     window.position[1]+row*128, 128, 128);
        }
    }
}




var events = function() {

    window.canvas.onmousemove = function(e) {

        if (e['buttons'] == 1) {

            window.position[0] += (e.clientX-window.mouse_position[0]);
            window.position[1] += (e.clientY-window.mouse_position[1]);

            if (window.position[0] >= 0) {
                window.position[0] = -128;
                window.coordinates[0] -= 1;
            } else if (window.position[0] < -128) {
                window.position[0] = 0;
                window.coordinates[0] += 1;
            }

            if (window.position[1] >= 0) {
                window.position[1] = -128;
                window.coordinates[1] -= 1;
            } else if (window.position[1] < -128) {
                window.position[1] = 0;
                window.coordinates[1] += 1;
            }

            render();
        }

        window.mouse_position[0] = e.clientX;
        window.mouse_position[1] = e.clientY;
    }
}


window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('load', resizeCanvas, false);
window.addEventListener('mousemove', events, false);

resizeCanvas();

To get better performance you should avoid changing the src of img nodes and move them around instead.

A simple way to minimize the number of img nodes handled and modified (except for screen positioning) is to use an LRU (Least Recently Used) cache.

Basically you keep a cache of last say 100 image nodes (they must be enough to cover at least one screen) by using a dictionary mapping the src url to a node object and also keeping them all in a doubly-linked list.

When a tile is required you first check in the cache, and if it's already there just move it to the front of LRU list and move the img coordinates, otherwise create a new node and set the source or, if you already hit the cache limit, reuse the last node in the doubly-linked list instead. In code:

function setTile(x, y, src) {
    var t = cache[src];
    if (!t) {
        if (cache_count == MAXCACHE) {
            t = lru_last;
            t.prev.next = null;
            lru_last = t.prev;
            t.prev = t.next = null;
            delete cache[t.src]
            t.src = src;
            t.img.src = src;
            cache[t.src] = t;
        } else {
            t = { prev: null,
                  next: null,
                  img: document.createElement("img") };
            t.src = src;
            t.img.src = src;
            t.img.className = "tile";
            scr.appendChild(t.img);
            cache[t.src] = t;
            cache_count += 1;
        }
    } else {
        if (t.prev) t.prev.next = t.next; else lru_first = t.next;
        if (t.next) t.next.prev = t.prev; else lru_last = t.prev;
    }
    t.prev = null; t.next = lru_first;
    if (t.next) t.next.prev = t; else lru_last = t;
    lru_first = t;

    t.img.style.left = x + "px";
    t.img.style.top = y + "px";
    scr.appendChild(t.img);
}

I'm also always appending the requested tile to the container so that it goes in front of all other existing tiles; this way I don't need to remove old tiles and they're simply left behind.

To update the screen I just iterate over all the tiles I need and request them:

function setView(x0, y0) {
    var w = scr.offsetWidth;
    var h = scr.offsetHeight;
    var iy0 = y0 >> 7;
    var ix0 = x0 >> 7;
    for (var y=iy0; y*128 < y0+h; y++) {
        for (var x=ix0; x*128 < x0+w; x++) {
            setTile(x*128-x0, y*128-y0, "tile_" + y + "_" + x + ".jpg");
        }
    }
}

most of the time the setTile request will just update the x and y coordinates of an existing img tag, without changing anything else. At the same time no more than MAXCACHE image nodes will be present on the screen.

You can see a full working example in

http://raksy.dyndns.org/tiles/tiles.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM