async: Skip wrapping with xpcall under lovejs

See Davidobot/love.js#54.

lovejs crashes love with "attempt to yield across metamethod/C-call
boundary" when you try to yield in a coroutine, so we skip wrapping.

We lose the coroutine-local callstack, but at least it works.

Here's my repro:

    local color = {
        purple = {0.25, 0.09, 0.28, 1},
        white = {0.89, 0.91, 0.90, 1},
    }
    local ball = {
        x = 100,
        y = 100,
        r = 20,
    }
    local S = {}
    local input = {
        vanilla = 'v',
        async = 'c',
        quit = 'escape',
    }

    function love.load()
        S.coro = async()
    end

    local function coro_fn()
        S.async_running = true
        for i=1,100 do
            color.purple[4] = i / 100
            coroutine.yield()
        end
        color.purple[4] = 1
        S.async_running = false
        return true
    end

    function love.update(dt)
        S.coro:update(dt)

        if S.vanilla then
            local success, result = coroutine.resume(S.vanilla)
            if result then
                print("coroutine cleared")
                S.vanilla = nil
            end
        end

        if not S.async_running then
            if love.keyboard.isDown(input.vanilla) then
                S.vanilla = coroutine.create(coro_fn)
                print("raw coroutine started")

            elseif love.keyboard.isDown(input.async) then
                print("Starting async call coroutine from update")
                S.coro:call(coro_fn)

            elseif love.keyboard.isDown(input.quit) then
                love.event.quit()
            end
        end
    end

    function love.draw()
        love.graphics.setColor(color.purple)
        love.graphics.circle("fill", ball.x, ball.y, ball.r)

        love.graphics.setColor(color.white)
        local str = "To start a coroutine:"
        for key,val in pairs(input) do
            str = ("%s\n%s: %s"):format(str, key, val)
        end
        love.graphics.printf(str, 5,5, 200, "left")
    end
This commit is contained in:
David Briscoe 2022-03-05 19:55:32 -08:00
parent f6a18380d4
commit 98f3630c07

View File

@ -27,16 +27,32 @@ function async:new()
self.tasks_stalled = {} self.tasks_stalled = {}
end end
local capture_callstacks
if love.system.getOS() == 'Web' then
-- Do no extra wrapping under lovejs because using xpcall causes "attempt
-- to yield across metamethod/C-call boundary"
capture_callstacks = function(f)
return f
end
else
capture_callstacks = function(f)
-- Report errors with the coroutine's callstack instead of one coming
-- from async:update.
return function(...)
local results = {xpcall(f, debug.traceback, ...)}
local success = table.remove(results, 1)
if not success then
error(table.remove(results, 1))
end
return unpack(results)
end
end
end
--add a task to the kernel --add a task to the kernel
function async:call(f, args, callback, error_callback) function async:call(f, args, callback, error_callback)
self:add(coroutine.create(function(...) f = capture_callstacks(f)
local results = {xpcall(f, debug.traceback, ...)} self:add(coroutine.create(f), args, callback, error_callback)
local success = table.remove(results, 1)
if not success then
error(table.remove(results, 1))
end
return unpack(results)
end), args, callback, error_callback)
end end
--add an already-existing coroutine to the kernel --add an already-existing coroutine to the kernel