From dc7420a61d07b5fb38187e5adc3af5cd87087f4d Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 13:08:28 +0300 Subject: [PATCH] =?UTF-8?q?fix(lua):=20require()=20no-op=20+=20=D1=83?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20Proxy?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20Instance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. require(): в Roblox загружает ModuleScript. У нас модулей нет — возвращаем mod как есть (если объект) или undefined. 2. Proxy улучшения: - Object.hasOwnProperty + 'in' checks: методы (WaitForChild, FindFirstChild и т.д.) точно не перехватываются; - Symbol-ключи всегда undefined; - System keys (then, catch, toString, constructor) → undefined чтобы wasmoon не пытался обращаться как с Promise/класс; - has() возвращает true для всех строковых ключей (избавляет от падений на 'if obj.SomeField then ...'). --- src/editor/engine/lua/RobloxShim.js | 60 ++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index 07cb736..126d5df 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -250,6 +250,21 @@ function makeStubSignal() { return sig; } +// Callable proxy: сам вызывается как function (ничего не делает), также имеет +// поля Connect/Disconnect и Fire/fire — то есть выглядит и как метод, и как +// сигнал, и как объект. Используется для unknown method-like свойств. +function makeStubCallable() { + const fn = function () { return undefined; }; + fn.__stub = true; + fn.Connect = function () { return { Disconnect: () => {}, disconnect: () => {}, Connected: false }; }; + fn.connect = fn.Connect; + fn.Fire = function () {}; + fn.fire = fn.Fire; + fn.Wait = function () { return null; }; + fn.wait = fn.Wait; + return fn; +} + // Эвристика: какие имена свойств вероятно сигналы? // В Roblox сигналы заканчиваются на: Changed, Added, Removed, Began, Ended, // Clicked, Activated, Touched, Selected, Deselected, Equipped, Unequipped, и т.д. @@ -290,30 +305,39 @@ function newInstance(className, name) { // Proxy: для unknown свойств возвращаем stub чтобы скрипты не падали. return new Proxy(target, { get(t, prop) { - if (prop in t) return t[prop]; + // Существующее свойство всегда возвращаем как есть (включая методы) + if (Object.prototype.hasOwnProperty.call(t, prop) || prop in t) { + return t[prop]; + } + // Не-строки и Symbol.* — undefined чтобы wasmoon не путался if (typeof prop !== 'string') return undefined; - // Системные/wasmoon-внутренние ключи — undefined чтобы wasmoon не путался - if (prop === 'then' || prop === 'catch' || prop === 'toJSON' || - prop === Symbol.toPrimitive || prop.startsWith('__')) { + // wasmoon JS-internal ключи — undefined + if (prop === 'then' || prop === 'catch' || prop === 'finally' || + prop === 'toJSON' || prop === 'toString' || prop === 'valueOf' || + prop === 'constructor' || prop === 'prototype' || + prop.startsWith('__') || prop.startsWith('Symbol')) { return undefined; } + // Эвристика: имена сигналов → stub-сигнал. Иначе — stub-Folder. + // Stub-Folder сам по себе callable (на случай если скрипт его вызовет + // как функцию: `foo()` вместо `foo:Connect()` — оба не падают). + let stub; if (isProbablySignalName(prop)) { - const stub = makeStubSignal(); - t[prop] = stub; - return stub; + stub = makeStubSignal(); + } else { + stub = newInstance('Folder', prop); } - // Иначе — child stub (Folder) который тоже выживет на чтении свойств - // и может иметь свои дочерние stub'ы. Cache в target чтобы тот же ссылочно. - const childStub = newInstance('Folder', prop); - t[prop] = childStub; - return childStub; + t[prop] = stub; + return stub; }, set(t, prop, value) { t[prop] = value; return true; }, has(t, prop) { - return prop in t || (typeof prop === 'string' && !prop.startsWith('__')); + // Для in-проверок отвечаем true почти всегда (чтобы Lua не падал на + // условиях вроде if obj.SomeField then ...) + return true; }, }); } @@ -485,6 +509,16 @@ export function registerRobloxShim(lua, opts) { send('log', { level: 'warn', text: args.map(stringify).join('\t') }); }); + // require(ModuleScript) — в Roblox загружает модуль. У нас модулей нет — + // возвращаем undefined (Lua nil) чтобы скрипты типа local mod = require(...) + // не падали. require строкой (стандартный Lua) перехватывать не будем. + global.set('require', (mod) => { + // Если передали Instance-stub — возвращаем сам stub (чтобы хоть + // что-то можно было сделать с возвращённым значением). + if (mod && typeof mod === 'object') return mod; + return undefined; + }); + // === task.* + wait === // task.wait/wait — реальный yield через coroutines. Юзер пишет: // while true do part.Position = ... ; task.wait(0.1) end