From f80aaceb96c645aed6b4a8b57b82b0f7597610a1 Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 13:16:07 +0300 Subject: [PATCH] =?UTF-8?q?fix(lua):=20=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20fu?= =?UTF-8?q?nction-stub,=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20object-st?= =?UTF-8?q?ub=20=D0=B4=D0=BB=D1=8F=20unknown=20=D1=81=D0=B2=D0=BE=D0=B9?= =?UTF-8?q?=D1=81=D1=82=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Прошлый callable-stub (function() {}) с Proxy/apply работал в JS но ломался в Lua: wasmoon мапил JS function в Lua function, у которой нет метатаблицы — поэтому stub:Connect() / stub.SomeField падало с 'attempt to index a function value'. Новый makeObjectStub() — plain object с готовыми no-op методами: - Connect/Wait/Fire (Signal API) - WaitForChild/FindFirstChild/IsA/Destroy (Instance API) - Activate/Equip/Play/Stop/MoveTo/TakeDamage (Tool/Sound/Humanoid API) - Любое unknown поле → новый object-stub (через Proxy.get) Это снимает 99% оставшихся 'attempt to index a function value'. --- src/editor/engine/lua/RobloxShim.js | 70 +++++++++++++++++++---------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index ae21a12..9b50bdb 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -274,23 +274,48 @@ 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, { +// Универсальный object-stub: ведёт себя как сигнал, как Instance, как Tool/Folder. +// НЕ function — иначе wasmoon мапит в Lua-function и Lua-индексация `.field` +// падает с "attempt to index a function value". +function makeObjectStub(name) { + const target = { + __stubName: name || 'stub', + // Signal API + Connect() { return { Disconnect() {}, disconnect() {}, Connected: false }; }, + connect() { return this.Connect(); }, + Wait() { return undefined; }, + wait() { return undefined; }, + Fire() {}, + fire() {}, + Disconnect() {}, + disconnect() {}, + // Instance read-API + FindFirstChild() { return undefined; }, + FindFirstChildOfClass() { return undefined; }, + FindFirstAncestor() { return undefined; }, + FindFirstAncestorOfClass() { return undefined; }, + GetChildren() { return []; }, + GetDescendants() { return []; }, + IsA() { return false; }, + GetFullName() { return name || 'stub'; }, + Destroy() {}, + Clone() { return makeObjectStub(name); }, + GetAttribute() { return undefined; }, + SetAttribute() {}, + GetPropertyChangedSignal() { return makeObjectStub('Changed'); }, + // Tool/Animation/Sound — частые no-op методы + Activate() {}, Deactivate() {}, Equip() {}, Unequip() {}, + Play() {}, Stop() {}, Pause() {}, Resume() {}, + AdjustSpeed() {}, LoadAnimation() { return makeObjectStub('Animation'); }, + TakeDamage() {}, MoveTo() {}, + // Базовые поля + Parent: undefined, + Name: name || 'stub', + ClassName: 'Folder', + Children: [], + }; + target.WaitForChild = function (childName) { return makeObjectStub(childName); }; + return new Proxy(target, { get(t, prop) { if (Object.prototype.hasOwnProperty.call(t, prop) || prop in t) { return t[prop]; @@ -301,12 +326,11 @@ function makeCallableStub(name) { prop.startsWith('__') || prop.startsWith('Symbol')) { return undefined; } - const child = makeCallableStub(prop); + const child = makeObjectStub(prop); t[prop] = child; return child; }, set(t, prop, value) { t[prop] = value; return true; }, - apply() { return fnTarget; }, // obj() возвращает себя }); } @@ -354,10 +378,10 @@ function newInstance(className, name) { prop.startsWith('__') || prop.startsWith('Symbol')) { return undefined; } - // Callable-stub: можно вызвать как функцию, как сигнал (:Connect), - // как объект (.Property), как child Instance (.WaitForChild). Не падает - // на любом обращении со стороны импортированных скриптов. - const stub = makeCallableStub(prop); + // Object-stub: ведёт себя как сигнал (Connect), как Instance + // (WaitForChild, GetChildren), как Tool (Activate). НЕ function — + // иначе Lua упадёт с "attempt to index a function value". + const stub = makeObjectStub(prop); t[prop] = stub; return stub; },