diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index 5136f53..efc236f 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -25,6 +25,11 @@ const SCHEDULER = { const HEARTBEAT_SIGNAL = makeSignal(); const STEPPED_SIGNAL = makeSignal(); +// Глобальный helper для запуска Lua-handler'ов в собственной coroutine. +// Без этого Roblox-обработчики которые внутри делают wait() падают с +// "attempt to yield across a C-call boundary". +let _runHandlerInCoroutine = null; + function makeSignal() { const sig = { __isSignal: true, @@ -45,9 +50,16 @@ function makeSignal() { sig.connect = sig.Connect; sig.Fire = function (...args) { for (const fn of [...sig.connections]) { - try { fn(...args); } catch (e) { - // eslint-disable-next-line no-console - console.error('[Signal handler]', e); + // Запускаем handler в его собственной coroutine — это позволяет + // делать wait() внутри без yield-across-C-boundary ошибки. + if (_runHandlerInCoroutine) { + try { _runHandlerInCoroutine(fn, args); } catch (e) { + console.error('[Signal handler]', e); + } + } else { + try { fn(...args); } catch (e) { + console.error('[Signal handler]', e); + } } } }; @@ -1389,7 +1401,29 @@ export function registerRobloxShim(lua, opts) { if type(ret) == 'number' then return ret end return 0 end + + -- Запуск Lua-handler'а в собственной coroutine. + -- Используется при Fire сигнала из JS — иначе wait() внутри handler'а + -- падает с 'attempt to yield across a C-call boundary'. + __rbxl_next_handler_id = 0 + function __rbxl_run_in_coroutine(fn, a1, a2, a3, a4) + __rbxl_next_handler_id = __rbxl_next_handler_id + 1 + local handlerId = "handler_" .. __rbxl_next_handler_id + local co = coroutine.create(function() fn(a1, a2, a3, a4) end) + __rbxl_register_coroutine(handlerId, co) + local ok, ret = coroutine.resume(co) + if not ok then + __rbxl_send_error(handlerId, tostring(ret)) + __rbxl_unregister_coroutine(handlerId) + elseif type(ret) == 'number' then + __rbxl_schedule_resume(handlerId, ret) + elseif coroutine.status(co) == 'dead' then + __rbxl_unregister_coroutine(handlerId) + end + end `); + // Кешируем ссылку на функцию для использования из makeSignal + _runHandlerInCoroutine = lua.global.get('__rbxl_run_in_coroutine'); // Добавим Lua-side helper для лога global.set('__log', (level, text) => { send('log', { level: String(level || 'info'), text: String(text || '') });