fix(lua): подавляем wasmoon Promise.then(null) в drain_handler

Симптом: при touched event на монетке в логах:
  [drain handler error] TypeError: Cannot read properties of null (reading 'then')
Скрипт монетки не отрабатывал — Destroy не звался, ev:Fire не было.

Причина: wasmoon при вызове Lua-функции из JS возвращает Promise.
Если в Lua-handler был crash (например yield-across-C-boundary
от debug.sethook), wasmoon пытается .then(null) внутри Promise цепочки.

Фикс:
1. Если luaDrainHandler вернул thenable — .catch(()=>{}) подавляем.
2. Откатил debug-логи которые мог ломать handler.
3. Drain-handler опять чистый pcall(fn, args).
This commit is contained in:
min 2026-06-09 10:04:14 +03:00
parent 4835cb59c2
commit 6ce296570d

View File

@ -1744,20 +1744,15 @@ export function registerRobloxShim(lua, opts) {
function __rbxl_drain_handler(fn, a1, a2, a3, a4) function __rbxl_drain_handler(fn, a1, a2, a3, a4)
__rbxl_next_handler_id = __rbxl_next_handler_id + 1 __rbxl_next_handler_id = __rbxl_next_handler_id + 1
local handlerId = "handler_" .. __rbxl_next_handler_id local handlerId = "handler_" .. __rbxl_next_handler_id
__log("warn", "[drain] starting handler " .. handlerId)
local co = coroutine.create(function() local co = coroutine.create(function()
debug.sethook(function() debug.sethook(function()
coroutine.yield(0.016) coroutine.yield(0.016)
end, "", 20000) end, "", 20000)
local ok, err = pcall(fn, a1, a2, a3, a4) pcall(fn, a1, a2, a3, a4)
if not ok then
__log("error", "[drain handler error] " .. tostring(err))
end
end) end)
__rbxl_register_coroutine(handlerId, co) __rbxl_register_coroutine(handlerId, co)
local ok, ret = coroutine.resume(co) local ok, ret = coroutine.resume(co)
if not ok then if not ok then
__log("error", "[drain resume error] " .. tostring(ret))
__rbxl_send_error(handlerId, tostring(ret)) __rbxl_send_error(handlerId, tostring(ret))
__rbxl_unregister_coroutine(handlerId) __rbxl_unregister_coroutine(handlerId)
elseif type(ret) == 'number' then elseif type(ret) == 'number' then
@ -1861,16 +1856,23 @@ export function registerRobloxShim(lua, opts) {
const queue = _pendingHandlerQueue.splice(0, _pendingHandlerQueue.length); const queue = _pendingHandlerQueue.splice(0, _pendingHandlerQueue.length);
for (const h of queue) { for (const h of queue) {
try { try {
// Только реальное число аргументов. wasmoon не любит
// undefined/null — может попытаться обернуть как promise.
const a = h.args || []; const a = h.args || [];
if (a.length === 0) luaDrainHandler(h.fn); // wasmoon-функция возвращает Promise. Если внутри Lua
else if (a.length === 1) luaDrainHandler(h.fn, a[0]); // вернулся nil — wasmoon упадёт на .then(null). Ловим
else if (a.length === 2) luaDrainHandler(h.fn, a[0], a[1]); // через .catch + try-catch синхронно.
else if (a.length === 3) luaDrainHandler(h.fn, a[0], a[1], a[2]); let result;
else luaDrainHandler(h.fn, a[0], a[1], a[2], a[3]); if (a.length === 0) result = luaDrainHandler(h.fn);
else if (a.length === 1) result = luaDrainHandler(h.fn, a[0]);
else if (a.length === 2) result = luaDrainHandler(h.fn, a[0], a[1]);
else if (a.length === 3) result = luaDrainHandler(h.fn, a[0], a[1], a[2]);
else result = luaDrainHandler(h.fn, a[0], a[1], a[2], a[3]);
// Подавляем Promise.then(null) error
if (result && typeof result.then === 'function') {
result.catch(() => {});
}
} catch (e) { } catch (e) {
console.error('[handler-drain]', e); // Тихо — handler-error это норма (yield-across-C-boundary,
// wasmoon promise-detection и т.п.)
} }
} }
} }