fix(rbxl): watchdog 100k→20k + откат pcall(yield) + batch 5+20ms

Прошлый коммит pcall(coroutine.yield) дал бесконечный цикл:
yield внутри C-call падал → pcall ловил → hook возвращался → счётчик
не сбросился → срабатывал опять моментально → вис.

Новая стратегия:
1. Голый coroutine.yield в watchdog: если внутри C-call упадёт с
   ошибкой — pcall(fn,...) внутри coroutine её поймает, скрипт
   завершится. Лучше чем вис.
2. Frequency 100k→20k инструкций — yield чаще, меньше времени на
   tight-loop перед уступкой управления UI.
3. Batch kickoff 20→5 скриптов с delay 20мс (было 0). 55 скриптов
   ROBLOX Battle = ~200мс распределено, UI отзывается.

Page-hang при init должен исчезнуть. Скрипты с tight-loop типа
WaitForChild через ChildAdded:wait() упадут с ошибкой про yield,
но не повесят страницу.
This commit is contained in:
min 2026-06-08 20:51:24 +03:00
parent 734521df72
commit 38d135586b
2 changed files with 9 additions and 13 deletions

View File

@ -115,8 +115,8 @@ export class LuaSharedSandbox {
// Запускаем main-loop сразу — он начнёт tick'ать как только будут coroutines.
this._lastTickAt = performance.now();
this._startMainLoop();
// Init батчами по 20 с yield между ними, чтобы UI не подвисал на 700+ скриптах.
const BATCH_SIZE = 20;
// Init батчами по 5 с задержкой 20мс между ними, чтобы UI отзывался.
const BATCH_SIZE = 5;
let idx = 0;
const initBatch = () => {
if (this._isStopped) return;
@ -130,7 +130,7 @@ export class LuaSharedSandbox {
}
idx = end;
if (idx < pending.length) {
setTimeout(initBatch, 0);
setTimeout(initBatch, 20);
} else {
// eslint-disable-next-line no-console
console.log(`[LuaSharedSandbox] all ${pending.length} scripts kicked off`);
@ -199,13 +199,11 @@ export class LuaSharedSandbox {
})
local co = coroutine.create(function()
-- WATCHDOG: каждые 100000 инструкций yield 1 кадр.
-- Защищает от tight-loop. yield обёрнут в pcall так как
-- внутри C-call boundary yield бросает ошибку но в этом
-- случае tight-loop наш hook просто будет вызываться позже
-- (когда Lua вернётся из C-call) и yield сработает.
-- НЕ оборачиваем в pcall внутри C-call boundary yield
-- упадёт ошибкой, что прервёт скрипт. Это лучше чем виснуть.
debug.sethook(function()
pcall(coroutine.yield, 0.016)
end, "", 100000)
coroutine.yield(0.016)
end, "", 20000)
-- pcall защищает от runtime-ошибок которые иначе крашат
-- coroutine и могут повредить WASM-стейт. Возвраты
-- handler'а намеренно поглощаются.

View File

@ -1642,11 +1642,9 @@ export function registerRobloxShim(lua, opts) {
-- (ok, ret1, ret2, ...) мы их не используем.
local co = coroutine.create(function()
-- Тот же watchdog что и в _startSingleScript.
-- yield обёрнут в pcall: внутри C-call boundary yield бросает
-- ошибку, но hook будет вызван позже когда Lua вернётся.
debug.sethook(function()
pcall(coroutine.yield, 0.016)
end, "", 100000)
coroutine.yield(0.016)
end, "", 20000)
pcall(fn, a1, a2, a3, a4)
end)
__rbxl_register_coroutine(handlerId, co)