fix(lua): require() no-op + улучшенный Proxy для Instance
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 ...').
This commit is contained in:
parent
59d0d86811
commit
dc7420a61d
@ -250,6 +250,21 @@ function makeStubSignal() {
|
|||||||
return sig;
|
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,
|
// В Roblox сигналы заканчиваются на: Changed, Added, Removed, Began, Ended,
|
||||||
// Clicked, Activated, Touched, Selected, Deselected, Equipped, Unequipped, и т.д.
|
// Clicked, Activated, Touched, Selected, Deselected, Equipped, Unequipped, и т.д.
|
||||||
@ -290,30 +305,39 @@ function newInstance(className, name) {
|
|||||||
// Proxy: для unknown свойств возвращаем stub чтобы скрипты не падали.
|
// Proxy: для unknown свойств возвращаем stub чтобы скрипты не падали.
|
||||||
return new Proxy(target, {
|
return new Proxy(target, {
|
||||||
get(t, prop) {
|
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;
|
if (typeof prop !== 'string') return undefined;
|
||||||
// Системные/wasmoon-внутренние ключи — undefined чтобы wasmoon не путался
|
// wasmoon JS-internal ключи — undefined
|
||||||
if (prop === 'then' || prop === 'catch' || prop === 'toJSON' ||
|
if (prop === 'then' || prop === 'catch' || prop === 'finally' ||
|
||||||
prop === Symbol.toPrimitive || prop.startsWith('__')) {
|
prop === 'toJSON' || prop === 'toString' || prop === 'valueOf' ||
|
||||||
|
prop === 'constructor' || prop === 'prototype' ||
|
||||||
|
prop.startsWith('__') || prop.startsWith('Symbol')) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
// Эвристика: имена сигналов → stub-сигнал. Иначе — stub-Folder.
|
||||||
|
// Stub-Folder сам по себе callable (на случай если скрипт его вызовет
|
||||||
|
// как функцию: `foo()` вместо `foo:Connect()` — оба не падают).
|
||||||
|
let stub;
|
||||||
if (isProbablySignalName(prop)) {
|
if (isProbablySignalName(prop)) {
|
||||||
const stub = makeStubSignal();
|
stub = makeStubSignal();
|
||||||
|
} else {
|
||||||
|
stub = newInstance('Folder', prop);
|
||||||
|
}
|
||||||
t[prop] = stub;
|
t[prop] = stub;
|
||||||
return stub;
|
return stub;
|
||||||
}
|
|
||||||
// Иначе — child stub (Folder) который тоже выживет на чтении свойств
|
|
||||||
// и может иметь свои дочерние stub'ы. Cache в target чтобы тот же ссылочно.
|
|
||||||
const childStub = newInstance('Folder', prop);
|
|
||||||
t[prop] = childStub;
|
|
||||||
return childStub;
|
|
||||||
},
|
},
|
||||||
set(t, prop, value) {
|
set(t, prop, value) {
|
||||||
t[prop] = value;
|
t[prop] = value;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
has(t, prop) {
|
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') });
|
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 ===
|
||||||
// task.wait/wait — реальный yield через coroutines. Юзер пишет:
|
// task.wait/wait — реальный yield через coroutines. Юзер пишет:
|
||||||
// while true do part.Position = ... ; task.wait(0.1) end
|
// while true do part.Position = ... ; task.wait(0.1) end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user