Page 1 of 1

[SOLVED][STI]crash when map:resize

Posted: Wed Jan 29, 2020 7:59 pm
by gotokiichi
I'm using karai17's STI library for my little project. Thanks karai17, it realy helps!
But when I try to implement zoom in or out the tiled map, the program just crashes every runtime without any error infomation to me. I have located the error to map:resize. If I delete this line, everything's ok and stable. I really need this function for zoom in and out the stage map, because the function map:draw with scale will also scale the drawsize of it, so that I need map:resize to change the drawarea back to original. Pls help and suggestion, thanks in advance!

I attach the love file and copy my urgly code as below:
mouse drag to pan the map
mouse wheel up/down to scale the map

Code: Select all

sti = require('sti')
map = sti("assets/map01.lua")
main_canvas = love.graphics.newCanvas()

Game = {}
Game.tileWidth = 48
Game.tileHeight = 48
Game.tileRowNum = 30
Game.tileColNum = 30
Game.xScale = 1
Game.yScale = 1
Game.windowWidth = 48*15
Game.windowHeight = 48*10

local cam, map
local flag_XYRecorded  
local dx, dy            
local x_start, y_start  
local scale --scale for sti
local main_canvas
local floor = math.floor

function clamp(value, min, max)
    if max then
        if value > max then return max end
    end
    if min then
        if value < min then return min end
    end
    return value
end

function initCamera()
    flag_XYRecorded = false
    dx, dy = 0, 0
    x_start, y_start = 0, 0
    scale = 1
end

function drawWorld(dx, dy)
    love.graphics.setCanvas(main_canvas)
    love.graphics.clear()
    width_change = floor(Game.tileRowNum*Game.tileWidth/scale)
    height_change = floor(Game.tileColNum*Game.tileHeight/scale)
    map:resize(width_change,height_change)
    map:draw(dx,dy,scale, scale)
    love.graphics.setCanvas()
    love.graphics.draw(main_canvas, 0, 0, 0, 1)
end

function love.wheelmoved(x, y)
    if y > 0 then
        --Mouse wheel moved up
        scale = scale + 0.1
    elseif y < 0 then
        --Mouse wheel moved down
        scale = scale - 0.1
    end
    scale = clamp(scale, 0.5, 1.0)
end

----------------

function love:load()
    map = sti("assets/map01.lua")

    initCamera()

    main_canvas =  love.graphics.newCanvas(Game.tileRowNum*Game.tileWidth, Game.tileColNum*Game.tileHeight)
end

function love:update(dt)
    -- print('update')
    if love.mouse.isDown(1) then -- mouse key1 pressed
        if not flag_XYRecorded then
            local x, y = love.mouse.getPosition()
            x_start = x - dx
            y_start = y - dy
            flag_XYRecorded = true
        else
            local x, y = love.mouse.getPosition()
            dx = x - x_start
            dy = y - y_start
        end
    else -- mouse key1 release
        if flag_XYRecorded then
            local x, y = love.mouse.getPosition()
            dx = x - x_start
            dy = y - y_start

            dx = clamp(dx, floor(Game.windowWidth/Game.xScale-Game.tileWidth*Game.tileRowNum), 0)
            dy = clamp(dy, floor(Game.windowHeight/Game.yScale-Game.tileHeight*Game.tileColNum), 0)

            flag_XYRecorded = false
        end
    end

    map:update(dt)
end

function love:draw()
    drawWorld(dx,dy)

    --debug info
    local x, y = love.mouse.getPosition()
    love.graphics.setColor(1, 0, 0)
    love.graphics.print('scr: '..x..' '..y, 10, 10)
    love.graphics.print('map: '..(x-dx*Game.xScale)..' '..(y-dy*Game.yScale), 10, 40)
    love.graphics.print('init: '..dx..' '..dy, 10, 70)
    love.graphics.print('start: '..x_start..' '..y_start, 10, 100)
    love.graphics.print('scale: '..scale, 10, 130)
    love.graphics.setColor(1, 1, 1)

end

Re: [STI]crash when map:resize

Posted: Thu Jan 30, 2020 1:20 am
by JJSax
I'm not seeing anywhere in there where it calls map:resize(). Perhaps you copied over the code that isn't crashing by mistake. It would surely help to see how you're calling it as well.

Edit: also let us know what the error you're getting is.

Re: [STI]crash when map:resize

Posted: Thu Jan 30, 2020 2:36 am
by gotokiichi
JJSax wrote: Thu Jan 30, 2020 1:20 am I'm not seeing anywhere in there where it calls map:resize(). Perhaps you copied over the code that isn't crashing by mistake. It would surely help to see how you're calling it as well.

Edit: also let us know what the error you're getting is.
Pls forgive a sleepy dog:P
I have changed the code and attached the runable love file.

Re: [STI]crash when map:resize

Posted: Thu Jan 30, 2020 3:06 am
by JJSax
gotokiichi wrote: Thu Jan 30, 2020 2:36 am Pls forgive a sleepy dog:P
I have changed the code and attached the runable love file.
You are definitely forgiven.

I'll preface this by saying I'm not an expert. But I think I fixed it and I have a theory of why it works. It didn't crash right away and just eventually had the error. I dug into the init for sti and found that Map:resize(w,h) sets a new canvas every time it's called. The wiki says that love.graphics.newCanvas can be slow when ran repeatedly, like in a draw loop like you had it. This not only would cause your FPS to drop, but I think it was causing some issues with running too many canvases. Here is what I did. (just the draw loop and the wheel moved functions)

Code: Select all

function drawWorld(dx, dy)
    love.graphics.setCanvas(main_canvas)
    love.graphics.clear()
    -- removed the resize function from here
    map:draw(dx,dy,scale, scale)
    love.graphics.setCanvas()
    love.graphics.draw(main_canvas, 0, 0, 0, 1)
end

function love.wheelmoved(x, y)
    if y > 0 then
        --Mouse wheel moved up
        scale = scale + 0.1
    elseif y < 0 then
        --Mouse wheel moved down
        scale = scale - 0.1
    end 
    -- put it here so it's not called all the time, only when it's needed.
    width_change = floor(Game.tileRowNum*Game.tileWidth/scale)
    height_change = floor(Game.tileColNum*Game.tileHeight/scale)
    map:resize(width_change,height_change)
    scale = clamp(scale, 0.5, 1.0)
end
Side note, is there a reason you set main_canvas at the top, then on a separate line make it local? It doesn't pose an issue as far as I know and you're not the only one who I've seen do it, but just curious why you don't set it local to begin with.

Re: [STI]crash when map:resize

Posted: Thu Jan 30, 2020 8:12 am
by gotokiichi
JJSax wrote: Thu Jan 30, 2020 3:06 am
gotokiichi wrote: Thu Jan 30, 2020 2:36 am Pls forgive a sleepy dog:P
I have changed the code and attached the runable love file.
You are definitely forgiven.

I'll preface this by saying I'm not an expert. But I think I fixed it and I have a theory of why it works. It didn't crash right away and just eventually had the error. I dug into the init for sti and found that Map:resize(w,h) sets a new canvas every time it's called. The wiki says that love.graphics.newCanvas can be slow when ran repeatedly, like in a draw loop like you had it. This not only would cause your FPS to drop, but I think it was causing some issues with running too many canvases. Here is what I did. (just the draw loop and the wheel moved functions)

Code: Select all

function drawWorld(dx, dy)
    love.graphics.setCanvas(main_canvas)
    love.graphics.clear()
    -- removed the resize function from here
    map:draw(dx,dy,scale, scale)
    love.graphics.setCanvas()
    love.graphics.draw(main_canvas, 0, 0, 0, 1)
end

function love.wheelmoved(x, y)
    if y > 0 then
        --Mouse wheel moved up
        scale = scale + 0.1
    elseif y < 0 then
        --Mouse wheel moved down
        scale = scale - 0.1
    end 
    -- put it here so it's not called all the time, only when it's needed.
    width_change = floor(Game.tileRowNum*Game.tileWidth/scale)
    height_change = floor(Game.tileColNum*Game.tileHeight/scale)
    map:resize(width_change,height_change)
    scale = clamp(scale, 0.5, 1.0)
end
Side note, is there a reason you set main_canvas at the top, then on a separate line make it local? It doesn't pose an issue as far as I know and you're not the only one who I've seen do it, but just curious why you don't set it local to begin with.
Thanks for you such prompt help! You are right, I made a test with counting map:resize time and every time the program just crashed on the 653th resizing. Moving map:resize to wheelmoved callback funtion as your suggestion would delay that crash but it still came on the 653th resizing. No body could escape the end:(

I think only I can do now is freezing the map scale functino to future.

And for that local canvas, there's no reason for that, I just follow BYTEPATH tutorial:D
You can find it here: https://github.com/adnzzzzZ/blog/issues/15

Re: [STI]crash when map:resize

Posted: Thu Jan 30, 2020 12:13 pm
by pgimeno
Yes, JJSax has correctly identified the source of the problem. To prevent it from happening, you can help the garbage collector a bit, e.g. by adding a collectgarbage() call after the resize.

For me it didn't crash, it just became more and more sluggish over time, but the problem disappeared after I added the collectgarbage() call.

The cause of the problem seems to be that LuaJIT does not have information about how many resources a certain object takes, and therefore it does not prioritize: it doesn't collect garbage until many objects (perhaps thousands of them) have accumulated. If those objects happen to be big canvases, the graphics card can't cope with so many of them.

Re: [STI]crash when map:resize

Posted: Thu Jan 30, 2020 2:05 pm
by gotokiichi
pgimeno wrote: Thu Jan 30, 2020 12:13 pm Yes, JJSax has correctly identified the source of the problem. To prevent it from happening, you can help the garbage collector a bit, e.g. by adding a collectgarbage() call after the resize.

For me it didn't crash, it just became more and more sluggish over time, but the problem disappeared after I added the collectgarbage() call.

The cause of the problem seems to be that LuaJIT does not have information about how many resources a certain object takes, and therefore it does not prioritize: it doesn't collect garbage until many objects (perhaps thousands of them) have accumulated. If those objects happen to be big canvases, the graphics card can't cope with so many of them.
Thanks pgimeno, I will take a try of collectgarbage()!
Yes, it works!

Re: [STI]crash when map:resize

Posted: Thu Jan 30, 2020 2:12 pm
by grump
Calling :release() on the old canvas should help as well and is a more localized solution for the problem at hand.

Re: [SOLVED][STI]crash when map:resize

Posted: Thu Jan 30, 2020 6:54 pm
by pgimeno
Eh, good point, thanks grump!

Only problem is that the canvas "belongs" to the lib, so you need to dig into the lib's internals. Maybe the STI maintainers can add code to the library to release the old canvas when creating a new one.

@ gotokiichi That's a guarantee against crashing, but as JJSax said, it's best if you minimize the map:resize() calls, limiting them to the instants where the zoom level changes. An alternative, or addendum, to what he did is to keep the last zoom value in a variable, and only call map:resize() when the zoom actually changes, i.e. when the last zoom differs from the newly calculated zoom.