From 8fe52dbe68bf098dd38a0e6e07ce9bce053a458c Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 13:10:26 +0300 Subject: [PATCH] =?UTF-8?q?fix(lua):=20universal=20callable-stub=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20unknown=20=D1=81=D0=B2=D0=BE=D0=B9=D1=81=D1=82?= =?UTF-8?q?=D0=B2=20Instance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Импортированные скрипты делают obj:Method(arg) где obj — stub. Раньше stub был просто Folder, его как функцию вызвать нельзя → self2 is not a function массово. Фикс: makeCallableStub() — Proxy на function(){} который: - вызывается как функция (apply trap) → возвращает себя; - имеет .Connect/.Disconnect/.Wait/.Fire → ведёт себя как сигнал; - любое .UnknownField → возвращает другой callable-stub; - .then/.catch/.constructor → undefined (wasmoon не путается). Этим закрывается основная масса остаточных падений в туториал-скриптах с цепочками вроде Tool.Handle:WaitForChild('X'):Connect(...) где Handle у нас отсутствует. --- src/editor/engine/lua/RobloxShim.js | 49 +++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index 126d5df..ae21a12 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -274,6 +274,42 @@ function isProbablySignalName(prop) { || /(Changed|Added|Removed|Began|Ended|Clicked|Activated|Touched|Died|Loaded|Hover|Connect|Event|Signal|Reached)$/.test(prop); } +// Callable stub: функция которая ничего не делает + поля Connect/Wait/Fire, +// чтобы выглядеть и как метод (`obj:Method()`), и как сигнал (`sig:Connect()`), +// и как объект (`obj.Property`). +function makeCallableStub(name) { + // Используем function чтобы Proxy мог иметь apply trap + const fnTarget = function () { return fnTarget; }; + fnTarget.__stubName = name || 'stub'; + fnTarget.Connect = function () { return { Disconnect: () => {}, disconnect: () => {}, Connected: false }; }; + fnTarget.connect = fnTarget.Connect; + fnTarget.Wait = function () { return undefined; }; + fnTarget.wait = fnTarget.Wait; + fnTarget.Fire = function () { return undefined; }; + fnTarget.fire = fnTarget.Fire; + fnTarget.Disconnect = function () {}; + fnTarget.disconnect = fnTarget.Disconnect; + // Proxy чтобы любое доступ к unknown полю/индексу возвращал тот же stub + return new Proxy(fnTarget, { + get(t, prop) { + if (Object.prototype.hasOwnProperty.call(t, prop) || prop in t) { + return t[prop]; + } + if (typeof prop !== 'string') return undefined; + if (prop === 'then' || prop === 'catch' || prop === 'finally' || + prop === 'toJSON' || prop === 'constructor' || prop === 'prototype' || + prop.startsWith('__') || prop.startsWith('Symbol')) { + return undefined; + } + const child = makeCallableStub(prop); + t[prop] = child; + return child; + }, + set(t, prop, value) { t[prop] = value; return true; }, + apply() { return fnTarget; }, // obj() возвращает себя + }); +} + function newInstance(className, name) { const m = makeInstanceMethods(); const target = { @@ -318,15 +354,10 @@ function newInstance(className, name) { prop.startsWith('__') || prop.startsWith('Symbol')) { return undefined; } - // Эвристика: имена сигналов → stub-сигнал. Иначе — stub-Folder. - // Stub-Folder сам по себе callable (на случай если скрипт его вызовет - // как функцию: `foo()` вместо `foo:Connect()` — оба не падают). - let stub; - if (isProbablySignalName(prop)) { - stub = makeStubSignal(); - } else { - stub = newInstance('Folder', prop); - } + // Callable-stub: можно вызвать как функцию, как сигнал (:Connect), + // как объект (.Property), как child Instance (.WaitForChild). Не падает + // на любом обращении со стороны импортированных скриптов. + const stub = makeCallableStub(prop); t[prop] = stub; return stub; },