fix(rbxl): tick/spawn/delay/LoadLibrary + SpecialMesh + pcall watchdog yield
Битва скриптов ROBLOX Battle вылетала на:
- tick() / time() / delay() / spawn() — старые Roblox globals, не было
- LoadLibrary('RbxUtility') — Roblox 2009 legacy, не было
- SpecialMesh.MeshType — класс не реализован, доступ к полю крашил
- attempt to yield across C-call boundary — debug.sethook yield без pcall
Фиксы:
1. Lua-prelude: tick=os.time, time=os.clock*1000, delay/spawn через
coroutine, LoadLibrary возвращает proxy-стаб через metatable.
2. Instance.new('SpecialMesh'/'BlockMesh'/'CylinderMesh'/'FileMesh')
стабы с MeshType/MeshId/Scale полями.
3. debug.sethook: pcall(coroutine.yield, ...) вместо голого yield —
если внутри C-call, ошибка молча проглатывается, hook сработает
позже когда Lua вернётся из C. Frequency 50k→100k.
4. script.Parent в Lua-обёртке: setmetatable __index → workspace
fallback для script.Foo:Bar() паттернов. Гарантия что
_scriptParent.Parent ~= nil.
ROBLOX Battle должна показать меньше errors на этом запуске.
This commit is contained in:
parent
932ef2bc20
commit
734521df72
@ -175,23 +175,37 @@ export class LuaSharedSandbox {
|
||||
}
|
||||
const wrapped = `
|
||||
do
|
||||
local script = {
|
||||
-- Если parentExpr вернул primitive — у него уже есть :FindFirstChild и пр.
|
||||
-- Если ничего не вернёт — workspace (всегда валидный).
|
||||
-- script.Parent.Parent (Tool.Parent = StarterPack / Backpack / workspace).
|
||||
local _scriptParent = ${parentExpr}
|
||||
if _scriptParent == nil then _scriptParent = workspace end
|
||||
if _scriptParent.Parent == nil then _scriptParent.Parent = workspace end
|
||||
local script = setmetatable({
|
||||
Name = ${JSON.stringify(scriptName)},
|
||||
Parent = ${parentExpr},
|
||||
Parent = _scriptParent,
|
||||
ClassName = "Script",
|
||||
Disabled = false,
|
||||
Source = nil,
|
||||
}
|
||||
}, {
|
||||
-- Любой доступ к несуществующему полю → workspace
|
||||
-- (на случай script.Foo:Bar() в старом коде)
|
||||
__index = function(t, k)
|
||||
if k == "FindFirstChild" or k == "WaitForChild" or k == "GetChildren" then
|
||||
return function() return nil end
|
||||
end
|
||||
return workspace[k]
|
||||
end,
|
||||
})
|
||||
local co = coroutine.create(function()
|
||||
-- WATCHDOG: каждые 50000 инструкций — yield 1 кадр.
|
||||
-- Защищает от tight-loop типа:
|
||||
-- while not parent:FindFirstChild(name) do
|
||||
-- parent.ChildAdded:wait()
|
||||
-- end
|
||||
-- где наш stub :wait() возвращает сразу.
|
||||
-- WATCHDOG: каждые 100000 инструкций — yield 1 кадр.
|
||||
-- Защищает от tight-loop. yield обёрнут в pcall так как
|
||||
-- внутри C-call boundary yield бросает ошибку — но в этом
|
||||
-- случае tight-loop наш hook просто будет вызываться позже
|
||||
-- (когда Lua вернётся из C-call) и yield сработает.
|
||||
debug.sethook(function()
|
||||
coroutine.yield(0.016)
|
||||
end, "", 50000)
|
||||
pcall(coroutine.yield, 0.016)
|
||||
end, "", 100000)
|
||||
-- pcall защищает от runtime-ошибок которые иначе крашат
|
||||
-- coroutine и могут повредить WASM-стейт. Возвраты
|
||||
-- handler'а намеренно поглощаются.
|
||||
|
||||
@ -1247,6 +1247,15 @@ export function registerRobloxShim(lua, opts) {
|
||||
inst.FireServer = function (...a) { this.OnServerEvent.Fire(localPlayer, ...a); };
|
||||
inst.FireClient = function (_p, ...a) { this.OnClientEvent.Fire(...a); };
|
||||
inst.FireAllClients = function (...a) { this.OnClientEvent.Fire(...a); };
|
||||
} else if (className === 'SpecialMesh' || className === 'BlockMesh'
|
||||
|| className === 'CylinderMesh' || className === 'FileMesh') {
|
||||
inst = newInstance(className, className);
|
||||
inst.MeshType = { Name: 'Brick', Value: 0 };
|
||||
inst.MeshId = '';
|
||||
inst.TextureId = '';
|
||||
inst.Scale = new RbxVector3(1, 1, 1);
|
||||
inst.Offset = new RbxVector3(0, 0, 0);
|
||||
inst.VertexColor = new RbxVector3(1, 1, 1);
|
||||
} else if (className === 'BindableEvent') {
|
||||
inst = newInstance('BindableEvent', 'BindableEvent');
|
||||
inst.Event = makeSignal();
|
||||
@ -1588,6 +1597,29 @@ export function registerRobloxShim(lua, opts) {
|
||||
end
|
||||
wait = rbx_wait
|
||||
|
||||
-- Roblox legacy globals
|
||||
tick = function() return os.time() end -- секунды с epoch
|
||||
time = function() return os.clock() * 1000 end -- ms аптайм
|
||||
delay = function(sec, fn) -- delay(sec, fn) — задержка + вызов
|
||||
if type(fn) ~= 'function' then return end
|
||||
local co = coroutine.create(function()
|
||||
rbx_wait(sec or 0)
|
||||
pcall(fn)
|
||||
end)
|
||||
coroutine.resume(co)
|
||||
end
|
||||
spawn = function(fn) -- spawn(fn) — запуск в отдельной coroutine
|
||||
if type(fn) ~= 'function' then return end
|
||||
local co = coroutine.create(function() pcall(fn) end)
|
||||
coroutine.resume(co)
|
||||
end
|
||||
-- LoadLibrary("RbxStamper"/"RbxUtility") — старый Roblox 2009.
|
||||
-- Возвращаем пустую таблицу-стаб чтобы скрипт не упал.
|
||||
LoadLibrary = function(name)
|
||||
return setmetatable({}, { __index = function() return function() end end })
|
||||
end
|
||||
require = require or function(_) return {} end
|
||||
|
||||
function __rbxl_resume_co(co)
|
||||
if not co or coroutine.status(co) ~= 'suspended' then return nil end
|
||||
local ok, ret = coroutine.resume(co)
|
||||
@ -1609,10 +1641,12 @@ export function registerRobloxShim(lua, opts) {
|
||||
-- что приводит к wasmoon promise-detection crash). pcall возвращает
|
||||
-- (ok, ret1, ret2, ...) — мы их не используем.
|
||||
local co = coroutine.create(function()
|
||||
-- Тот же watchdog что и в _startSingleScript
|
||||
-- Тот же watchdog что и в _startSingleScript.
|
||||
-- yield обёрнут в pcall: внутри C-call boundary yield бросает
|
||||
-- ошибку, но hook будет вызван позже когда Lua вернётся.
|
||||
debug.sethook(function()
|
||||
coroutine.yield(0.016)
|
||||
end, "", 50000)
|
||||
pcall(coroutine.yield, 0.016)
|
||||
end, "", 100000)
|
||||
pcall(fn, a1, a2, a3, a4)
|
||||
end)
|
||||
__rbxl_register_coroutine(handlerId, co)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user