wasmoon
Version:
A real lua VM with JS bindings made with webassembly
1,081 lines (1,067 loc) • 152 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.wasmoon = {}));
})(this, (function (exports) { 'use strict';
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
exports.LuaReturn = void 0;
(function (LuaReturn) {
LuaReturn[LuaReturn["Ok"] = 0] = "Ok";
LuaReturn[LuaReturn["Yield"] = 1] = "Yield";
LuaReturn[LuaReturn["ErrorRun"] = 2] = "ErrorRun";
LuaReturn[LuaReturn["ErrorSyntax"] = 3] = "ErrorSyntax";
LuaReturn[LuaReturn["ErrorMem"] = 4] = "ErrorMem";
LuaReturn[LuaReturn["ErrorErr"] = 5] = "ErrorErr";
LuaReturn[LuaReturn["ErrorFile"] = 6] = "ErrorFile";
})(exports.LuaReturn || (exports.LuaReturn = {}));
const PointerSize = 4;
const LUA_MULTRET = -1;
const LUAI_MAXSTACK = 1000000;
const LUA_REGISTRYINDEX = -LUAI_MAXSTACK - 1000;
exports.LuaType = void 0;
(function (LuaType) {
LuaType[LuaType["None"] = -1] = "None";
LuaType[LuaType["Nil"] = 0] = "Nil";
LuaType[LuaType["Boolean"] = 1] = "Boolean";
LuaType[LuaType["LightUserdata"] = 2] = "LightUserdata";
LuaType[LuaType["Number"] = 3] = "Number";
LuaType[LuaType["String"] = 4] = "String";
LuaType[LuaType["Table"] = 5] = "Table";
LuaType[LuaType["Function"] = 6] = "Function";
LuaType[LuaType["Userdata"] = 7] = "Userdata";
LuaType[LuaType["Thread"] = 8] = "Thread";
})(exports.LuaType || (exports.LuaType = {}));
exports.LuaEventCodes = void 0;
(function (LuaEventCodes) {
LuaEventCodes[LuaEventCodes["Call"] = 0] = "Call";
LuaEventCodes[LuaEventCodes["Ret"] = 1] = "Ret";
LuaEventCodes[LuaEventCodes["Line"] = 2] = "Line";
LuaEventCodes[LuaEventCodes["Count"] = 3] = "Count";
LuaEventCodes[LuaEventCodes["TailCall"] = 4] = "TailCall";
})(exports.LuaEventCodes || (exports.LuaEventCodes = {}));
exports.LuaEventMasks = void 0;
(function (LuaEventMasks) {
LuaEventMasks[LuaEventMasks["Call"] = 1] = "Call";
LuaEventMasks[LuaEventMasks["Ret"] = 2] = "Ret";
LuaEventMasks[LuaEventMasks["Line"] = 4] = "Line";
LuaEventMasks[LuaEventMasks["Count"] = 8] = "Count";
})(exports.LuaEventMasks || (exports.LuaEventMasks = {}));
exports.LuaLibraries = void 0;
(function (LuaLibraries) {
LuaLibraries["Base"] = "_G";
LuaLibraries["Coroutine"] = "coroutine";
LuaLibraries["Table"] = "table";
LuaLibraries["IO"] = "io";
LuaLibraries["OS"] = "os";
LuaLibraries["String"] = "string";
LuaLibraries["UTF8"] = "utf8";
LuaLibraries["Math"] = "math";
LuaLibraries["Debug"] = "debug";
LuaLibraries["Package"] = "package";
})(exports.LuaLibraries || (exports.LuaLibraries = {}));
class LuaTimeoutError extends Error {
}
class Decoration {
constructor(target, options) {
this.target = target;
this.options = options;
}
}
function decorate(target, options) {
return new Decoration(target, options);
}
class Pointer extends Number {
}
class MultiReturn extends Array {
}
const INSTRUCTION_HOOK_COUNT = 1000;
class Thread {
constructor(lua, typeExtensions, address, parent) {
this.closed = false;
this.lua = lua;
this.typeExtensions = typeExtensions;
this.address = address;
this.parent = parent;
}
newThread() {
const address = this.lua.lua_newthread(this.address);
if (!address) {
throw new Error('lua_newthread returned a null pointer');
}
return new Thread(this.lua, this.typeExtensions, address, this.parent || this);
}
resetThread() {
this.assertOk(this.lua.lua_resetthread(this.address));
}
loadString(luaCode, name) {
const size = this.lua.module.lengthBytesUTF8(luaCode);
const pointerSize = size + 1;
const bufferPointer = this.lua.module._malloc(pointerSize);
try {
this.lua.module.stringToUTF8(luaCode, bufferPointer, pointerSize);
this.assertOk(this.lua.luaL_loadbufferx(this.address, bufferPointer, size, name !== null && name !== void 0 ? name : bufferPointer, null));
}
finally {
this.lua.module._free(bufferPointer);
}
}
loadFile(filename) {
this.assertOk(this.lua.luaL_loadfilex(this.address, filename, null));
}
resume(argCount = 0) {
const dataPointer = this.lua.module._malloc(PointerSize);
try {
this.lua.module.setValue(dataPointer, 0, 'i32');
const luaResult = this.lua.lua_resume(this.address, null, argCount, dataPointer);
return {
result: luaResult,
resultCount: this.lua.module.getValue(dataPointer, 'i32'),
};
}
finally {
this.lua.module._free(dataPointer);
}
}
getTop() {
return this.lua.lua_gettop(this.address);
}
setTop(index) {
this.lua.lua_settop(this.address, index);
}
remove(index) {
return this.lua.lua_remove(this.address, index);
}
setField(index, name, value) {
index = this.lua.lua_absindex(this.address, index);
this.pushValue(value);
this.lua.lua_setfield(this.address, index, name);
}
async run(argCount = 0, options) {
const originalTimeout = this.timeout;
try {
if ((options === null || options === void 0 ? void 0 : options.timeout) !== undefined) {
this.setTimeout(Date.now() + options.timeout);
}
let resumeResult = this.resume(argCount);
while (resumeResult.result === exports.LuaReturn.Yield) {
if (this.timeout && Date.now() > this.timeout) {
if (resumeResult.resultCount > 0) {
this.pop(resumeResult.resultCount);
}
throw new LuaTimeoutError(`thread timeout exceeded`);
}
if (resumeResult.resultCount > 0) {
const lastValue = this.getValue(-1);
this.pop(resumeResult.resultCount);
if (lastValue === Promise.resolve(lastValue)) {
await lastValue;
}
else {
await new Promise((resolve) => setImmediate(resolve));
}
}
else {
await new Promise((resolve) => setImmediate(resolve));
}
resumeResult = this.resume(0);
}
this.assertOk(resumeResult.result);
return this.getStackValues();
}
finally {
if ((options === null || options === void 0 ? void 0 : options.timeout) !== undefined) {
this.setTimeout(originalTimeout);
}
}
}
runSync(argCount = 0) {
const base = this.getTop() - argCount - 1;
this.assertOk(this.lua.lua_pcallk(this.address, argCount, LUA_MULTRET, 0, 0, null));
return this.getStackValues(base);
}
pop(count = 1) {
this.lua.lua_pop(this.address, count);
}
call(name, ...args) {
const type = this.lua.lua_getglobal(this.address, name);
if (type !== exports.LuaType.Function) {
throw new Error(`A function of type '${type}' was pushed, expected is ${exports.LuaType.Function}`);
}
for (const arg of args) {
this.pushValue(arg);
}
const base = this.getTop() - args.length - 1;
this.lua.lua_callk(this.address, args.length, LUA_MULTRET, 0, null);
return this.getStackValues(base);
}
getStackValues(start = 0) {
const returns = this.getTop() - start;
const returnValues = new MultiReturn(returns);
for (let i = 0; i < returns; i++) {
returnValues[i] = this.getValue(start + i + 1);
}
return returnValues;
}
stateToThread(L) {
var _a;
return L === ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.address) ? this.parent : new Thread(this.lua, this.typeExtensions, L, this.parent || this);
}
pushValue(rawValue, userdata) {
const decoratedValue = this.getValueDecorations(rawValue);
const target = decoratedValue.target;
if (target instanceof Thread) {
const isMain = this.lua.lua_pushthread(target.address) === 1;
if (!isMain) {
this.lua.lua_xmove(target.address, this.address, 1);
}
return;
}
const startTop = this.getTop();
switch (typeof target) {
case 'undefined':
this.lua.lua_pushnil(this.address);
break;
case 'number':
if (Number.isInteger(target)) {
this.lua.lua_pushinteger(this.address, BigInt(target));
}
else {
this.lua.lua_pushnumber(this.address, target);
}
break;
case 'string':
this.lua.lua_pushstring(this.address, target);
break;
case 'boolean':
this.lua.lua_pushboolean(this.address, target ? 1 : 0);
break;
default:
if (!this.typeExtensions.find((wrapper) => wrapper.extension.pushValue(this, decoratedValue, userdata))) {
throw new Error(`The type '${typeof target}' is not supported by Lua`);
}
}
if (decoratedValue.options.metatable) {
this.setMetatable(-1, decoratedValue.options.metatable);
}
if (this.getTop() !== startTop + 1) {
throw new Error(`pushValue expected stack size ${startTop + 1}, got ${this.getTop()}`);
}
}
setMetatable(index, metatable) {
index = this.lua.lua_absindex(this.address, index);
if (this.lua.lua_getmetatable(this.address, index)) {
this.pop(1);
const name = this.getMetatableName(index);
throw new Error(`data already has associated metatable: ${name || 'unknown name'}`);
}
this.pushValue(metatable);
this.lua.lua_setmetatable(this.address, index);
}
getMetatableName(index) {
const metatableNameType = this.lua.luaL_getmetafield(this.address, index, '__name');
if (metatableNameType === exports.LuaType.Nil) {
return undefined;
}
if (metatableNameType !== exports.LuaType.String) {
this.pop(1);
return undefined;
}
const name = this.lua.lua_tolstring(this.address, -1, null);
this.pop(1);
return name;
}
getValue(index, inputType, userdata) {
index = this.lua.lua_absindex(this.address, index);
const type = inputType !== null && inputType !== void 0 ? inputType : this.lua.lua_type(this.address, index);
switch (type) {
case exports.LuaType.None:
return undefined;
case exports.LuaType.Nil:
return null;
case exports.LuaType.Number:
return this.lua.lua_tonumberx(this.address, index, null);
case exports.LuaType.String:
return this.lua.lua_tolstring(this.address, index, null);
case exports.LuaType.Boolean:
return Boolean(this.lua.lua_toboolean(this.address, index));
case exports.LuaType.Thread:
return this.stateToThread(this.lua.lua_tothread(this.address, index));
default: {
let metatableName;
if (type === exports.LuaType.Table || type === exports.LuaType.Userdata) {
metatableName = this.getMetatableName(index);
}
const typeExtensionWrapper = this.typeExtensions.find((wrapper) => wrapper.extension.isType(this, index, type, metatableName));
if (typeExtensionWrapper) {
return typeExtensionWrapper.extension.getValue(this, index, userdata);
}
console.warn(`The type '${this.lua.lua_typename(this.address, type)}' returned is not supported on JS`);
return new Pointer(this.lua.lua_topointer(this.address, index));
}
}
}
close() {
if (this.isClosed()) {
return;
}
if (this.hookFunctionPointer) {
this.lua.module.removeFunction(this.hookFunctionPointer);
}
this.closed = true;
}
setTimeout(timeout) {
if (timeout && timeout > 0) {
if (!this.hookFunctionPointer) {
this.hookFunctionPointer = this.lua.module.addFunction(() => {
if (Date.now() > timeout) {
this.pushValue(new LuaTimeoutError(`thread timeout exceeded`));
this.lua.lua_error(this.address);
}
}, 'vii');
}
this.lua.lua_sethook(this.address, this.hookFunctionPointer, exports.LuaEventMasks.Count, INSTRUCTION_HOOK_COUNT);
this.timeout = timeout;
}
else if (this.hookFunctionPointer) {
this.hookFunctionPointer = undefined;
this.timeout = undefined;
this.lua.lua_sethook(this.address, null, 0, 0);
}
}
getTimeout() {
return this.timeout;
}
getPointer(index) {
return new Pointer(this.lua.lua_topointer(this.address, index));
}
isClosed() {
var _a;
return !this.address || this.closed || Boolean((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isClosed());
}
indexToString(index) {
const str = this.lua.luaL_tolstring(this.address, index, null);
this.pop();
return str;
}
dumpStack(log = console.log) {
const top = this.getTop();
for (let i = 1; i <= top; i++) {
const type = this.lua.lua_type(this.address, i);
const typename = this.lua.lua_typename(this.address, type);
const pointer = this.getPointer(i);
const name = this.indexToString(i);
const value = this.getValue(i, type);
log(i, typename, pointer, name, value);
}
}
assertOk(result) {
if (result !== exports.LuaReturn.Ok && result !== exports.LuaReturn.Yield) {
const resultString = exports.LuaReturn[result];
const error = new Error(`Lua Error(${resultString}/${result})`);
if (this.getTop() > 0) {
if (result === exports.LuaReturn.ErrorMem) {
error.message = this.lua.lua_tolstring(this.address, -1, null);
}
else {
const luaError = this.getValue(-1);
if (luaError instanceof Error) {
error.stack = luaError.stack;
}
error.message = this.indexToString(-1);
}
}
if (result !== exports.LuaReturn.ErrorMem) {
try {
this.lua.luaL_traceback(this.address, this.address, null, 1);
const traceback = this.lua.lua_tolstring(this.address, -1, null);
if (traceback.trim() !== 'stack traceback:') {
error.message = `${error.message}\n${traceback}`;
}
this.pop(1);
}
catch (err) {
console.warn('Failed to generate stack trace', err);
}
}
throw error;
}
}
getValueDecorations(value) {
return value instanceof Decoration ? value : new Decoration(value, {});
}
}
class Global extends Thread {
constructor(cmodule, shouldTraceAllocations) {
if (shouldTraceAllocations) {
const memoryStats = { memoryUsed: 0 };
const allocatorFunctionPointer = cmodule.module.addFunction((_userData, pointer, oldSize, newSize) => {
if (newSize === 0) {
if (pointer) {
memoryStats.memoryUsed -= oldSize;
cmodule.module._free(pointer);
}
return 0;
}
const endMemoryDelta = pointer ? newSize - oldSize : newSize;
const endMemory = memoryStats.memoryUsed + endMemoryDelta;
if (newSize > oldSize && memoryStats.memoryMax && endMemory > memoryStats.memoryMax) {
return 0;
}
const reallocated = cmodule.module._realloc(pointer, newSize);
if (reallocated) {
memoryStats.memoryUsed = endMemory;
}
return reallocated;
}, 'iiiii');
const address = cmodule.lua_newstate(allocatorFunctionPointer, null);
if (!address) {
cmodule.module.removeFunction(allocatorFunctionPointer);
throw new Error('lua_newstate returned a null pointer');
}
super(cmodule, [], address);
this.memoryStats = memoryStats;
this.allocatorFunctionPointer = allocatorFunctionPointer;
}
else {
super(cmodule, [], cmodule.luaL_newstate());
}
if (this.isClosed()) {
throw new Error('Global state could not be created (probably due to lack of memory)');
}
}
close() {
if (this.isClosed()) {
return;
}
super.close();
this.lua.lua_close(this.address);
if (this.allocatorFunctionPointer) {
this.lua.module.removeFunction(this.allocatorFunctionPointer);
}
for (const wrapper of this.typeExtensions) {
wrapper.extension.close();
}
}
registerTypeExtension(priority, extension) {
this.typeExtensions.push({ extension, priority });
this.typeExtensions.sort((a, b) => b.priority - a.priority);
}
loadLibrary(library) {
switch (library) {
case exports.LuaLibraries.Base:
this.lua.luaopen_base(this.address);
break;
case exports.LuaLibraries.Coroutine:
this.lua.luaopen_coroutine(this.address);
break;
case exports.LuaLibraries.Table:
this.lua.luaopen_table(this.address);
break;
case exports.LuaLibraries.IO:
this.lua.luaopen_io(this.address);
break;
case exports.LuaLibraries.OS:
this.lua.luaopen_os(this.address);
break;
case exports.LuaLibraries.String:
this.lua.luaopen_string(this.address);
break;
case exports.LuaLibraries.UTF8:
this.lua.luaopen_string(this.address);
break;
case exports.LuaLibraries.Math:
this.lua.luaopen_math(this.address);
break;
case exports.LuaLibraries.Debug:
this.lua.luaopen_debug(this.address);
break;
case exports.LuaLibraries.Package:
this.lua.luaopen_package(this.address);
break;
}
this.lua.lua_setglobal(this.address, library);
}
get(name) {
const type = this.lua.lua_getglobal(this.address, name);
const value = this.getValue(-1, type);
this.pop();
return value;
}
set(name, value) {
this.pushValue(value);
this.lua.lua_setglobal(this.address, name);
}
getTable(name, callback) {
const startStackTop = this.getTop();
const type = this.lua.lua_getglobal(this.address, name);
try {
if (type !== exports.LuaType.Table) {
throw new TypeError(`Unexpected type in ${name}. Expected ${exports.LuaType[exports.LuaType.Table]}. Got ${exports.LuaType[type]}.`);
}
callback(startStackTop + 1);
}
finally {
if (this.getTop() !== startStackTop + 1) {
console.warn(`getTable: expected stack size ${startStackTop} got ${this.getTop()}`);
}
this.setTop(startStackTop);
}
}
getMemoryUsed() {
return this.getMemoryStatsRef().memoryUsed;
}
getMemoryMax() {
return this.getMemoryStatsRef().memoryMax;
}
setMemoryMax(max) {
this.getMemoryStatsRef().memoryMax = max;
}
getMemoryStatsRef() {
if (!this.memoryStats) {
throw new Error('Memory allocations is not being traced, please build engine with { traceAllocations: true }');
}
return this.memoryStats;
}
}
class LuaTypeExtension {
constructor(thread, name) {
this.thread = thread;
this.name = name;
}
isType(_thread, _index, type, name) {
return type === exports.LuaType.Userdata && name === this.name;
}
getValue(thread, index, _userdata) {
const refUserdata = thread.lua.luaL_testudata(thread.address, index, this.name);
if (!refUserdata) {
throw new Error(`data does not have the expected metatable: ${this.name}`);
}
const referencePointer = thread.lua.module.getValue(refUserdata, '*');
return thread.lua.getRef(referencePointer);
}
pushValue(thread, decoratedValue, _userdata) {
const { target } = decoratedValue;
const pointer = thread.lua.ref(target);
const userDataPointer = thread.lua.lua_newuserdatauv(thread.address, PointerSize, 0);
thread.lua.module.setValue(userDataPointer, pointer, '*');
if (exports.LuaType.Nil === thread.lua.luaL_getmetatable(thread.address, this.name)) {
thread.pop(2);
throw new Error(`metatable not found: ${this.name}`);
}
thread.lua.lua_setmetatable(thread.address, -2);
return true;
}
}
class ErrorTypeExtension extends LuaTypeExtension {
constructor(thread, injectObject) {
super(thread, 'js_error');
this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => {
const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name);
const referencePointer = thread.lua.module.getValue(userDataPointer, '*');
thread.lua.unref(referencePointer);
return exports.LuaReturn.Ok;
}, 'ii');
if (thread.lua.luaL_newmetatable(thread.address, this.name)) {
const metatableIndex = thread.lua.lua_gettop(thread.address);
thread.lua.lua_pushstring(thread.address, 'protected metatable');
thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable');
thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0);
thread.lua.lua_setfield(thread.address, metatableIndex, '__gc');
thread.pushValue((jsRefError, key) => {
if (key === 'message') {
return jsRefError.message;
}
return null;
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__index');
thread.pushValue((jsRefError) => {
return jsRefError.message;
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__tostring');
}
thread.lua.lua_pop(thread.address, 1);
if (injectObject) {
thread.set('Error', {
create: (message) => {
if (message && typeof message !== 'string') {
throw new Error('message must be a string');
}
return new Error(message);
},
});
}
}
pushValue(thread, decoration) {
if (!(decoration.target instanceof Error)) {
return false;
}
return super.pushValue(thread, decoration);
}
close() {
this.thread.lua.module.removeFunction(this.gcPointer);
}
}
function createTypeExtension$6(thread, injectObject) {
return new ErrorTypeExtension(thread, injectObject);
}
class RawResult {
constructor(count) {
this.count = count;
}
}
function decorateFunction(target, options) {
return new Decoration(target, options);
}
class FunctionTypeExtension extends LuaTypeExtension {
constructor(thread, options) {
super(thread, 'js_function');
this.functionRegistry = typeof FinalizationRegistry !== 'undefined'
? new FinalizationRegistry((func) => {
if (!this.thread.isClosed()) {
this.thread.lua.luaL_unref(this.thread.address, LUA_REGISTRYINDEX, func);
}
})
: undefined;
this.options = options;
this.callbackContext = thread.newThread();
this.callbackContextIndex = this.thread.lua.luaL_ref(thread.address, LUA_REGISTRYINDEX);
if (!this.functionRegistry) {
console.warn('FunctionTypeExtension: FinalizationRegistry not found. Memory leaks likely.');
}
this.gcPointer = thread.lua.module.addFunction((calledL) => {
thread.lua.luaL_checkudata(calledL, 1, this.name);
const userDataPointer = thread.lua.luaL_checkudata(calledL, 1, this.name);
const referencePointer = thread.lua.module.getValue(userDataPointer, '*');
thread.lua.unref(referencePointer);
return exports.LuaReturn.Ok;
}, 'ii');
if (thread.lua.luaL_newmetatable(thread.address, this.name)) {
thread.lua.lua_pushstring(thread.address, '__gc');
thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0);
thread.lua.lua_settable(thread.address, -3);
thread.lua.lua_pushstring(thread.address, '__metatable');
thread.lua.lua_pushstring(thread.address, 'protected metatable');
thread.lua.lua_settable(thread.address, -3);
}
thread.lua.lua_pop(thread.address, 1);
this.functionWrapper = thread.lua.module.addFunction((calledL) => {
const calledThread = thread.stateToThread(calledL);
const refUserdata = thread.lua.luaL_checkudata(calledL, thread.lua.lua_upvalueindex(1), this.name);
const refPointer = thread.lua.module.getValue(refUserdata, '*');
const { target, options } = thread.lua.getRef(refPointer);
const argsQuantity = calledThread.getTop();
const args = [];
if (options.receiveThread) {
args.push(calledThread);
}
if (options.receiveArgsQuantity) {
args.push(argsQuantity);
}
else {
for (let i = 1; i <= argsQuantity; i++) {
const value = calledThread.getValue(i);
if (i !== 1 || !(options === null || options === void 0 ? void 0 : options.self) || value !== options.self) {
args.push(value);
}
}
}
try {
const result = target.apply(options === null || options === void 0 ? void 0 : options.self, args);
if (result === undefined) {
return 0;
}
else if (result instanceof RawResult) {
return result.count;
}
else if (result instanceof MultiReturn) {
for (const item of result) {
calledThread.pushValue(item);
}
return result.length;
}
else {
calledThread.pushValue(result);
return 1;
}
}
catch (err) {
if (err === Infinity) {
throw err;
}
calledThread.pushValue(err);
return calledThread.lua.lua_error(calledThread.address);
}
}, 'ii');
}
close() {
this.thread.lua.module.removeFunction(this.gcPointer);
this.thread.lua.module.removeFunction(this.functionWrapper);
this.callbackContext.close();
this.callbackContext.lua.luaL_unref(this.callbackContext.address, LUA_REGISTRYINDEX, this.callbackContextIndex);
}
isType(_thread, _index, type) {
return type === exports.LuaType.Function;
}
pushValue(thread, decoration) {
if (typeof decoration.target !== 'function') {
return false;
}
const pointer = thread.lua.ref(decoration);
const userDataPointer = thread.lua.lua_newuserdatauv(thread.address, PointerSize, 0);
thread.lua.module.setValue(userDataPointer, pointer, '*');
if (exports.LuaType.Nil === thread.lua.luaL_getmetatable(thread.address, this.name)) {
thread.pop(1);
thread.lua.unref(pointer);
throw new Error(`metatable not found: ${this.name}`);
}
thread.lua.lua_setmetatable(thread.address, -2);
thread.lua.lua_pushcclosure(thread.address, this.functionWrapper, 1);
return true;
}
getValue(thread, index) {
var _a;
thread.lua.lua_pushvalue(thread.address, index);
const func = thread.lua.luaL_ref(thread.address, LUA_REGISTRYINDEX);
const jsFunc = (...args) => {
var _a;
if (this.callbackContext.isClosed()) {
console.warn('Tried to call a function after closing lua state');
return;
}
const callThread = this.callbackContext.newThread();
try {
const internalType = callThread.lua.lua_rawgeti(callThread.address, LUA_REGISTRYINDEX, BigInt(func));
if (internalType !== exports.LuaType.Function) {
const callMetafieldType = callThread.lua.luaL_getmetafield(callThread.address, -1, '__call');
callThread.pop();
if (callMetafieldType !== exports.LuaType.Function) {
throw new Error(`A value of type '${internalType}' was pushed but it is not callable`);
}
}
for (const arg of args) {
callThread.pushValue(arg);
}
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.functionTimeout) {
callThread.setTimeout(Date.now() + this.options.functionTimeout);
}
const status = callThread.lua.lua_pcallk(callThread.address, args.length, 1, 0, 0, null);
if (status === exports.LuaReturn.Yield) {
throw new Error('cannot yield in callbacks from javascript');
}
callThread.assertOk(status);
if (callThread.getTop() > 0) {
return callThread.getValue(-1);
}
return undefined;
}
finally {
callThread.close();
this.callbackContext.pop();
}
};
(_a = this.functionRegistry) === null || _a === void 0 ? void 0 : _a.register(jsFunc, func);
return jsFunc;
}
}
function createTypeExtension$5(thread, options) {
return new FunctionTypeExtension(thread, options);
}
class NullTypeExtension extends LuaTypeExtension {
constructor(thread) {
super(thread, 'js_null');
this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => {
const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name);
const referencePointer = thread.lua.module.getValue(userDataPointer, '*');
thread.lua.unref(referencePointer);
return exports.LuaReturn.Ok;
}, 'ii');
if (thread.lua.luaL_newmetatable(thread.address, this.name)) {
const metatableIndex = thread.lua.lua_gettop(thread.address);
thread.lua.lua_pushstring(thread.address, 'protected metatable');
thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable');
thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0);
thread.lua.lua_setfield(thread.address, metatableIndex, '__gc');
thread.pushValue(() => null);
thread.lua.lua_setfield(thread.address, metatableIndex, '__index');
thread.pushValue(() => 'null');
thread.lua.lua_setfield(thread.address, metatableIndex, '__tostring');
thread.pushValue((self, other) => self === other);
thread.lua.lua_setfield(thread.address, metatableIndex, '__eq');
}
thread.lua.lua_pop(thread.address, 1);
super.pushValue(thread, new Decoration({}, {}));
thread.lua.lua_setglobal(thread.address, 'null');
}
getValue(thread, index) {
const refUserData = thread.lua.luaL_testudata(thread.address, index, this.name);
if (!refUserData) {
throw new Error(`data does not have the expected metatable: ${this.name}`);
}
return null;
}
pushValue(thread, decoration) {
if ((decoration === null || decoration === void 0 ? void 0 : decoration.target) !== null) {
return false;
}
thread.lua.lua_getglobal(thread.address, 'null');
return true;
}
close() {
this.thread.lua.module.removeFunction(this.gcPointer);
}
}
function createTypeExtension$4(thread) {
return new NullTypeExtension(thread);
}
class PromiseTypeExtension extends LuaTypeExtension {
constructor(thread, injectObject) {
super(thread, 'js_promise');
this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => {
const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name);
const referencePointer = thread.lua.module.getValue(userDataPointer, '*');
thread.lua.unref(referencePointer);
return exports.LuaReturn.Ok;
}, 'ii');
if (thread.lua.luaL_newmetatable(thread.address, this.name)) {
const metatableIndex = thread.lua.lua_gettop(thread.address);
thread.lua.lua_pushstring(thread.address, 'protected metatable');
thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable');
thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0);
thread.lua.lua_setfield(thread.address, metatableIndex, '__gc');
const checkSelf = (self) => {
if (Promise.resolve(self) !== self && typeof self.then !== 'function') {
throw new Error('promise method called without self instance');
}
return true;
};
thread.pushValue({
next: (self, ...args) => checkSelf(self) && self.then(...args),
catch: (self, ...args) => checkSelf(self) && self.catch(...args),
finally: (self, ...args) => checkSelf(self) && self.finally(...args),
await: decorateFunction((functionThread, self) => {
checkSelf(self);
if (functionThread.address === thread.address) {
throw new Error('cannot await in the main thread');
}
let promiseResult = undefined;
const awaitPromise = self
.then((res) => {
promiseResult = { status: 'fulfilled', value: res };
})
.catch((err) => {
promiseResult = { status: 'rejected', value: err };
});
const continuance = this.thread.lua.module.addFunction((continuanceState) => {
if (!promiseResult) {
return thread.lua.lua_yieldk(functionThread.address, 0, 0, continuance);
}
this.thread.lua.module.removeFunction(continuance);
const continuanceThread = thread.stateToThread(continuanceState);
if (promiseResult.status === 'rejected') {
continuanceThread.pushValue(promiseResult.value || new Error('promise rejected with no error'));
return this.thread.lua.lua_error(continuanceState);
}
if (promiseResult.value instanceof RawResult) {
return promiseResult.value.count;
}
else if (promiseResult.value instanceof MultiReturn) {
for (const arg of promiseResult.value) {
continuanceThread.pushValue(arg);
}
return promiseResult.value.length;
}
else {
continuanceThread.pushValue(promiseResult.value);
return 1;
}
}, 'iiii');
functionThread.pushValue(awaitPromise);
return new RawResult(thread.lua.lua_yieldk(functionThread.address, 1, 0, continuance));
}, { receiveThread: true }),
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__index');
thread.pushValue((self, other) => self === other);
thread.lua.lua_setfield(thread.address, metatableIndex, '__eq');
}
thread.lua.lua_pop(thread.address, 1);
if (injectObject) {
thread.set('Promise', {
create: (callback) => new Promise(callback),
all: (promiseArray) => {
if (!Array.isArray(promiseArray)) {
throw new Error('argument must be an array of promises');
}
return Promise.all(promiseArray.map((potentialPromise) => Promise.resolve(potentialPromise)));
},
resolve: (value) => Promise.resolve(value),
});
}
}
close() {
this.thread.lua.module.removeFunction(this.gcPointer);
}
pushValue(thread, decoration) {
if (Promise.resolve(decoration.target) !== decoration.target && typeof decoration.target.then !== 'function') {
return false;
}
return super.pushValue(thread, decoration);
}
}
function createTypeExtension$3(thread, injectObject) {
return new PromiseTypeExtension(thread, injectObject);
}
function decorateProxy(target, options) {
return new Decoration(target, options || {});
}
class ProxyTypeExtension extends LuaTypeExtension {
constructor(thread) {
super(thread, 'js_proxy');
this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => {
const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name);
const referencePointer = thread.lua.module.getValue(userDataPointer, '*');
thread.lua.unref(referencePointer);
return exports.LuaReturn.Ok;
}, 'ii');
if (thread.lua.luaL_newmetatable(thread.address, this.name)) {
const metatableIndex = thread.lua.lua_gettop(thread.address);
thread.lua.lua_pushstring(thread.address, 'protected metatable');
thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable');
thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0);
thread.lua.lua_setfield(thread.address, metatableIndex, '__gc');
thread.pushValue((self, key) => {
switch (typeof key) {
case 'number':
key = key - 1;
case 'string':
break;
default:
throw new Error('Only strings or numbers can index js objects');
}
const value = self[key];
if (typeof value === 'function') {
return decorateFunction(value, { self });
}
return value;
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__index');
thread.pushValue((self, key, value) => {
switch (typeof key) {
case 'number':
key = key - 1;
case 'string':
break;
default:
throw new Error('Only strings or numbers can index js objects');
}
self[key] = value;
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__newindex');
thread.pushValue((self) => {
var _a, _b;
return (_b = (_a = self.toString) === null || _a === void 0 ? void 0 : _a.call(self)) !== null && _b !== void 0 ? _b : typeof self;
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__tostring');
thread.pushValue((self) => {
return self.length || 0;
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__len');
thread.pushValue((self) => {
const keys = Object.getOwnPropertyNames(self);
let i = 0;
return MultiReturn.of(() => {
const ret = MultiReturn.of(keys[i], self[keys[i]]);
i++;
return ret;
}, self, null);
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__pairs');
thread.pushValue((self, other) => {
return self === other;
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__eq');
thread.pushValue((self, ...args) => {
if (args[0] === self) {
args.shift();
}
return self(...args);
});
thread.lua.lua_setfield(thread.address, metatableIndex, '__call');
}
thread.lua.lua_pop(thread.address, 1);
}
isType(_thread, _index, type, name) {
return type === exports.LuaType.Userdata && name === this.name;
}
getValue(thread, index) {
const refUserdata = thread.lua.lua_touserdata(thread.address, index);
const referencePointer = thread.lua.module.getValue(refUserdata, '*');
return thread.lua.getRef(referencePointer);
}
pushValue(thread, decoratedValue) {
var _a;
const { target, options } = decoratedValue;
if (options.proxy === undefined) {
if (target === null || target === undefined) {
return false;
}
if (typeof target !== 'object') {
const isClass = typeof target === 'function' && ((_a = target.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === target && target.toString().startsWith('class ');
if (!isClass) {
return false;
}
}
if (Promise.resolve(target) === target || typeof target.then === 'function') {
return false;
}
}
else if (options.proxy === false) {
return false;
}
if (options.metatable && !(options.metatable instanceof Decoration)) {
decoratedValue.options.metatable = decorateProxy(options.metatable, { proxy: false });
return false;
}
return super.pushValue(thread, decoratedValue);
}
close() {
this.thread.lua.module.removeFunction(this.gcPointer);
}
}
function createTypeExtension$2(thread) {
return new ProxyTypeExtension(thread);
}
class TableTypeExtension extends LuaTypeExtension {
constructor(thread) {
super(thread, 'js_table');
}
close() {
}
isType(_thread, _index, type) {
return type === exports.LuaType.Table;
}
getValue(thread, index, userdata) {
const seenMap = userdata || new Map();
const pointer = thread.lua.lua_topointer(thread.address, index);
let table = seenMap.get(pointer);
if (!table) {
const keys = this.readTableKeys(thread, index);
const isSequential = keys.length > 0 && keys.every((key, index) => key === String(index + 1));
table = isSequential ? [] : {};
seenMap.set(pointer, table);
this.readTableValues(thread, index, seenMap, table);
}
return table;
}
pushValue(thread, { target }, userdata) {
if (typeof target !== 'object' || target === null) {
return false;
}
const seenMap = userdata || new Map();
const existingReference = seenMap.get(target);
if (existingReference !== undefined) {
thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, BigInt(existingReference));
return true;
}
try {
const tableIndex = thread.getTop() + 1;
const create