【问题标题】:The stack of a lua coroutine is entered implicitly without a call to resume?一个lua协程的栈是隐式进入的,没有调用resume?
【发布时间】:2013-01-03 12:07:49
【问题描述】:

我正在使用 lua 协程 (lua 5.1) 为应用程序创建插件系统。我希望使用协程,以便插件可以像一个单独的应用程序一样运行,每个处理帧产生一次。插件程序通常遵循如下公式:

function Program(P)
    -- setup --
    NewDrawer(function()
        -- this gets rendered in a window for this plugin program --
        drawstuff(howeveryouwant)
    end)
    -- loop --
    local continue = true
    while continue do
        -- frame by frame stuff excluding rendering (handled by NewDrawer) --
        P = coroutine.yield()
    end
end

每个插件在应用程序的主循环中每帧恢复一次。然后,当开始绘图时,每个插件都有一个单独的窗口,它会在其中执行传递给 NewDrawer 的函数。

类似这样的:

while MainContinue do
    -- other stuff left out --
    ExecutePluginFrames() -- all plugin coroutines resumed once

    BeginRendering()
    -- other stuff left out --
    RenderPluginWindows() -- functions passed to NewDrawer called.
    EndRendering()
end

但是我发现,每当渲染中发生错误时,这突然开始表现得很奇怪,并弄乱了我原本强大的错误处理系统。我花了一点时间来理解正在发生的事情,但似乎我期望在主线程的调用堆栈中的对 WIN:Draw() 的调用(因为它由主应用程序处理)实际上是导致隐式跳转到协程的调用堆栈。

最初的问题是程序突然关闭,没有有用的错误输出。然后在查看插件程序中定义的渲染函数的堆栈回溯后,我发现从主线程到窗口的 Draw 的所有内容都不存在,并且该 yield 在调用堆栈中。

似乎因为窗口是在线程和绘图函数中创建的,所以它们正在由该线程的调用堆栈处理,这是一个问题,因为这意味着它们在主线程中设置的 pcall 之外。

这会发生吗?它是 C 源代码中的错误/快捷方式的结果吗?我做错了什么或至少做得不够正确?有没有办法干净利落地处理?

【问题讨论】:

  • 很难回答你的问题,因为你没有展示你如何打电话/恢复Program。您能否简化代码以显示与应用程序其余部分的交互?
  • 添加了编辑,它在 RenderPluginWindows() 中似乎正在发生调用堆栈的隐式变化。

标签: error-handling lua callstack implicit coroutine


【解决方案1】:

我无法重现您所描述的效果。这是我正在运行的代码:

local drawer = {}
function NewDrawer(func)
  table.insert(drawer, func)
end

function Program(P)
    NewDrawer(function()
        print("inside program", P)
    end)
    -- loop --
    local continue = true
    while continue do
        -- frame by frame stuff excluding rendering (handled by NewDrawer) --
        P = coroutine.yield()
    end
end

local coro = coroutine.create(Program)
local MainContinue = true
while MainContinue do
    -- other stuff left out --
    -- ExecutePluginFrames() -- all plugin coroutines resumed once
    coroutine.resume(coro, math.random(10))
    -- RenderPluginWindows() -- functions passed to NewDrawer called.
    for _, plugin in ipairs(drawer) do
      plugin()
    end
    MainContinue = false
end

当我单步执行代码并查看堆栈时,NewDrawer 中设置的回调在“主”线程中被调用。如果您调用返回当前线程的coroutine.running()nil 如果您在主线程中,您可以自己查看。

【讨论】:

  • 嗯,它的行为符合预期。好吧,我没想到这些事情会有所作为,但这里有一些其他可能的原因: 插件在不同的环境中执行。对传递给 NewDrawer 的绘图函数的调用是在 C 函数中进行的。 C 函数将完整的用户数据传递给它调用的函数,该函数是在插件程序中创建的。
  • 由于您不能共享您的代码供其他人执行,我认为您需要在您的 resume/yield 命令周围添加print 语句以及对coroutine.running() 的调用来弄清楚你看到的开关发生在哪里。我经常使用协程(将它们用于debugging and live coding),但从未见过您所描述的情况。
【解决方案2】:

我发现了为什么会发生这种情况。调用传递给 NewDrawer 的函数的渲染对象在创建时(通过 c 代码)使用指向创建它们的 lua 状态的指针进行初始化,这用于访问它们关联的 lua 数据并用于调用绘图函数。我没有看到 lua_State 和协程之间的联系。因此事实证明,如果 C 代码导致函数在 yield 之后在堆栈中被调用。

就解决方案而言,我决定将程序分成两个协程,一个用于渲染,一个用于处理。这解决了这个问题,允许渲染对象的创建线程同时是调用线程,并保持渲染循环和处理循环独立的优点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-05
    • 1970-01-01
    • 2023-02-13
    • 1970-01-01
    • 2012-04-03
    • 1970-01-01
    • 2013-09-29
    • 2017-06-03
    相关资源
    最近更新 更多