Стек сопрограммы lua вводится неявно без вызова возобновления?

Я использую сопрограммы 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? я что-то делаю не так или, по крайней мере, недостаточно правильно? есть ли способ справиться с этим чисто?


person Community    schedule 20.01.2013    source источник
comment
Сложно ответить на ваш вопрос, так как вы не показываете, как вы звоните / продолжаете Program. Можете ли вы упростить свой код, чтобы показать взаимодействие с остальной частью вашего приложения?   -  person Paul Kulchenko    schedule 20.01.2013
comment
Добавлены правки, это в RenderPluginWindows (), где, по-видимому, происходит неявное изменение в стеке вызовов.   -  person    schedule 21.01.2013


Ответы (2)


Я не могу воспроизвести эффект, который вы описываете. Это код, который я запускаю:

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, если вы находитесь внутри основного потока.

person Paul Kulchenko    schedule 21.01.2013
comment
Хм, хорошо, что ведет себя так, как ожидалось. Что ж, я не ожидал, что эти вещи будут иметь значение, но вот несколько других возможных причин: Плагин выполняется в другой среде. Вызов функций рисования, переданных в NewDrawer, выполняется в функции C. А функция C передает полные пользовательские данные вызываемой функции, которая создается внутри программы плагина. - person ; 21.01.2013
comment
Поскольку вы не можете поделиться своим кодом для выполнения другими, я думаю, вам нужно прибегнуть к добавлению операторов print к командам резюме / yield, а также к вызовам coroutine.running(), чтобы выяснить, где происходит переключение, которое вы видите. Я регулярно работаю с сопрограммами (использую их для отладки и кодирования в реальном времени), но никогда не видел такой ситуации вы описываете. - person Paul Kulchenko; 21.01.2013

Я выяснил, почему это происходило в моем случае. Объекты рендеринга, которые вызвали функцию, переданную в NewDrawer, инициализируются при создании (кодом c) указателем на состояние lua, которое их создало, и это используется для доступа к связанным с ними данным lua и для вызова функции рисования. Я не видел связи между lua_State и сопрограммами. Таким образом, оказывается, что функции могут вызываться в стеке после yield, если их вызывает код C.

Что касается решения, я решил разбить программу на две сопрограммы, одну для рендеринга и одну для обработки. Это решает проблему, позволяя потоку создания объектов рендеринга также быть вызывающим потоком, и сохраняет преимущества независимости цикла рендеринга и цикла обработки.

person Community    schedule 21.01.2013