fix(lua): Signal.Fire через очередь handler'ов в tickScheduler
Прошлый фикс с __rbxl_run_in_coroutine падал внутри wasmoon с 'Cannot read properties of null (reading then)' — wasmoon PromiseTypeExtension тщетно ловит return от Lua-функции. Новая стратегия: 1. Signal.Fire не запускает handler синхронно — складывает в JS-очередь _pendingHandlerQueue. 2. tickScheduler в начале каждого тика drain'ит очередь, для каждого handler'а вызывает Lua-функцию __rbxl_drain_handler в coroutine. 3. Поскольку tickScheduler уже стоит на main loop (не из JS-callback), wait() внутри handler'а корректно yield'ится в свою coroutine. Это разрешает: - Roblox-обработчики с wait() внутри (Tool.Equipped с reload-таймаут) - Любые цепочки signal:Connect → wait → action - Стандартные шаблоны Roblox-Lua flow.
This commit is contained in:
parent
d750c94a78
commit
03a6c357d0
@ -25,10 +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;
|
||||
// Очередь handler'ов которые надо запустить на следующем tickScheduler.
|
||||
// Этим мы выходим из C-boundary — wait() внутри handler'а становится
|
||||
// безопасным yield в собственной coroutine, потому что handler стартует
|
||||
// уже из main loop, а не из синхронного JS-callback.
|
||||
const _pendingHandlerQueue = [];
|
||||
|
||||
function makeSignal() {
|
||||
const sig = {
|
||||
@ -50,17 +51,10 @@ function makeSignal() {
|
||||
sig.connect = sig.Connect;
|
||||
sig.Fire = function (...args) {
|
||||
for (const fn of [...sig.connections]) {
|
||||
// Запускаем 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);
|
||||
}
|
||||
}
|
||||
// Кладём в очередь, чтобы handler стартовал не в текущем
|
||||
// JS-callback (откуда yield запрещён), а из tickScheduler
|
||||
// в своей coroutine. Безопасно для wait() внутри.
|
||||
_pendingHandlerQueue.push({ fn, args });
|
||||
}
|
||||
};
|
||||
sig.fire = sig.Fire;
|
||||
@ -1402,11 +1396,11 @@ export function registerRobloxShim(lua, opts) {
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Запуск Lua-handler'а в собственной coroutine.
|
||||
-- Используется при Fire сигнала из JS — иначе wait() внутри handler'а
|
||||
-- падает с 'attempt to yield across a C-call boundary'.
|
||||
-- Запуск Lua-handler'а из очереди в собственной coroutine.
|
||||
-- Вызывается из JS tickScheduler — мы УЖЕ вышли из C-callback,
|
||||
-- так что wait() внутри handler'а — yield в свою coroutine.
|
||||
__rbxl_next_handler_id = 0
|
||||
function __rbxl_run_in_coroutine(fn, a1, a2, a3, a4)
|
||||
function __rbxl_drain_handler(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)
|
||||
@ -1422,8 +1416,8 @@ export function registerRobloxShim(lua, opts) {
|
||||
end
|
||||
end
|
||||
`);
|
||||
// Кешируем ссылку на функцию для использования из makeSignal
|
||||
_runHandlerInCoroutine = lua.global.get('__rbxl_run_in_coroutine');
|
||||
// Кешируем ссылку на Lua-функцию запуска handler'а
|
||||
const luaDrainHandler = lua.global.get('__rbxl_drain_handler');
|
||||
// Добавим Lua-side helper для лога
|
||||
global.set('__log', (level, text) => {
|
||||
send('log', { level: String(level || 'info'), text: String(text || '') });
|
||||
@ -1469,7 +1463,20 @@ export function registerRobloxShim(lua, opts) {
|
||||
onDataSnapshot() {},
|
||||
|
||||
tickScheduler(_dt) {
|
||||
// 0. Tweens
|
||||
// 0a. Lua-handlers из очереди (signal.Fire отложил их сюда).
|
||||
// Запускаем каждый в своей coroutine — wait() внутри безопасен.
|
||||
if (_pendingHandlerQueue.length > 0) {
|
||||
const queue = _pendingHandlerQueue.splice(0, _pendingHandlerQueue.length);
|
||||
for (const h of queue) {
|
||||
try {
|
||||
const a = h.args || [];
|
||||
luaDrainHandler(h.fn, a[0], a[1], a[2], a[3]);
|
||||
} catch (e) {
|
||||
console.error('[handler-drain]', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 0b. Tweens
|
||||
_stepTweens(_dt);
|
||||
const now = SCHEDULER.now();
|
||||
// 1. task.delay / task.defer
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user