feat(rbxl): XML-������ ������ .rbxl + Day/Night + Tool/Mouse/Backpack flow #38

Closed
min wants to merge 39 commits from feat/rbxl-xml-parser-import into main
Showing only changes of commit 0d7224a2b8 - Show all commits

View File

@ -734,10 +734,11 @@ export function registerRobloxShim(lua, opts) {
}); });
}); });
// Лагалейн Lua-код: определяем task.wait, wait, и обёртку для скрипта. // Lua-prelude: task.wait через coroutine.yield + готовая resume-функция.
// Этот код выполняется ВНУТРИ Lua VM. // Главное: __rbxl_resume_co определена в Lua и вызывается из JS через
// lua.global.get('__rbxl_resume_co') — это безопаснее чем doStringSync
// потому что не парсит код заново и не создаёт re-entrant проблем.
lua.doStringSync(` lua.doStringSync(`
-- task.wait(sec) coroutine.yield(sec); main-loop вернёт через delay sec
local function rbx_wait(sec) local function rbx_wait(sec)
sec = sec or 0 sec = sec or 0
coroutine.yield(sec) coroutine.yield(sec)
@ -749,7 +750,23 @@ export function registerRobloxShim(lua, opts) {
task = { wait = rbx_wait } task = { wait = rbx_wait }
end end
wait = rbx_wait wait = rbx_wait
-- Вызывается из JS-tickScheduler:
-- возвращает next-delay (number) если co yield'нулся ещё раз,
-- или nil если co завершился / умер.
function __rbxl_resume_co(co)
if not co or coroutine.status(co) ~= 'suspended' then return nil end
local ok, ret = coroutine.resume(co)
if not ok then
return false, tostring(ret)
end
if coroutine.status(co) == 'dead' then return nil end
if type(ret) == 'number' then return ret end
return 0
end
`); `);
// Достаём ссылку на Lua-функцию один раз; вызовы безопасны (не doStringSync)
const luaResumeCo = lua.global.get('__rbxl_resume_co');
// === Setter Part-свойств (Position/Size/Color/...) === // === Setter Part-свойств (Position/Size/Color/...) ===
// Юзер пишет: part.Position = Vector3.new(0, 10, 0) // Юзер пишет: part.Position = Vector3.new(0, 10, 0)
@ -805,39 +822,32 @@ export function registerRobloxShim(lua, opts) {
} }
} }
// 2. Резюм coroutine'ов которые task.wait() // 2. Резюм coroutine'ов которые task.wait()
// Скрипт-coroutine при первом запуске yield'ит с delay (от task.wait). // Используем lua.global.get-кешированную __rbxl_resume_co функцию —
// Мы регистрируем delay через __rbxl_schedule_resume? Нет — проще: // безопаснее чем doStringSync (не re-entrant в WASM).
// отслеживаем последний yield-результат через coroutine.resume → возвращает const dueCoros = [];
// delay. После init скрипта проверим всех coroutines: если status==suspended, for (let i = waitingCoros.length - 1; i >= 0; i--) {
// и им пора — резюмируем. if (waitingCoros[i].wakeAt <= now) {
for (const [coId, co] of coroutines) { dueCoros.push(waitingCoros[i]);
let entry = waitingCoros.find(w => w.coId === coId); waitingCoros.splice(i, 1);
if (!entry) {
// Первый раз видим этот co — нужно вытащить delay из yield-результата.
// Это сделано в _onYield ниже.
continue;
} }
if (entry.wakeAt > now) continue; }
// Время резюмить for (const entry of dueCoros) {
waitingCoros.splice(waitingCoros.indexOf(entry), 1); const co = coroutines.get(entry.coId);
if (!co) continue;
try { try {
const code = ` const result = luaResumeCo(co);
local co = __rbxl_get_co(${JSON.stringify(coId)}) // result: number (next delay), nil (done), false+errStr (failed)
if co and coroutine.status(co) == 'suspended' then if (result === null || result === undefined) {
local ok, ret = coroutine.resume(co) coroutines.delete(entry.coId);
if not ok then } else if (typeof result === 'number') {
__rbxl_send_error(${JSON.stringify(coId)}, tostring(ret)) waitingCoros.push({
__rbxl_unregister_coroutine(${JSON.stringify(coId)}) coId: entry.coId,
elseif type(ret) == 'number' then wakeAt: SCHEDULER.now() + result * 1000,
__rbxl_schedule_resume(${JSON.stringify(coId)}, ret) });
elseif coroutine.status(co) == 'dead' then }
__rbxl_unregister_coroutine(${JSON.stringify(coId)})
end
end
`;
lua.doStringSync(code);
} catch (e) { } catch (e) {
send('log', { level: 'error', text: `[coroutine resume ${coId}] ${e?.message || e}` }); send('log', { level: 'error', text: `[coroutine ${entry.coId}] ${e?.message || e}` });
coroutines.delete(entry.coId);
} }
} }
}, },