From dd990becf4f6a9edf02b0904fff57626b69bff4c Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 15 Dec 2024 23:07:35 +0700 Subject: [PATCH] More native Lua work --- .../ru/dbotthepony/kstarbound/lua/LuaJNR.java | 50 +++++ .../ru/dbotthepony/kstarbound/Starbound.kt | 31 ++- .../dbotthepony/kstarbound/lua/Conversions.kt | 200 +++++++++++++++++- .../dbotthepony/kstarbound/lua/LuaThread.kt | 90 +++++++- .../ru/dbotthepony/kstarbound/lua/LuaType.kt | 141 +++++++++++- 5 files changed, 497 insertions(+), 15 deletions(-) diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java index 43fe7539..3e5fd412 100644 --- a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java +++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java @@ -4,6 +4,7 @@ import com.kenai.jffi.Closure; import jnr.ffi.LibraryLoader; import jnr.ffi.Pointer; import jnr.ffi.Runtime; +import jnr.ffi.annotations.IgnoreError; import jnr.ffi.annotations.LongLong; import org.jetbrains.annotations.NotNull; @@ -28,8 +29,11 @@ public interface LuaJNR { * LUA_ERRERR: error while running the message handler. * LUA_ERRGCMM: error while running a __gc metamethod. For such errors, Lua does not call the message handler (as this kind of error typically has no relation with the function being called). */ + @IgnoreError public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback); + @IgnoreError public int lua_callk(@NotNull Pointer luaState, int numArgs, int numResults, @LongLong long ctx, @LongLong long callback); + @IgnoreError public long lua_atpanic(@NotNull Pointer luaState, @LongLong long fn); /** @@ -38,10 +42,14 @@ public interface LuaJNR { * There is no explicit function to close or to destroy a thread. Threads are subject to garbage collection, like any Lua object. */ @NotNull + @IgnoreError public Pointer lua_newthread(@NotNull Pointer luaState); + @IgnoreError public int luaL_ref(@NotNull Pointer luaState, int stackIndex); + @IgnoreError public void lua_copy(@NotNull Pointer luaState, int fromIndex, int toIndex); + @IgnoreError public int lua_xmove(@NotNull Pointer from, @NotNull Pointer to, int amount); @Nullable @@ -49,30 +57,44 @@ public interface LuaJNR { public void lua_close(@NotNull Pointer luaState); // Стандартные библиотеки + @IgnoreError public void luaopen_base(@NotNull Pointer luaState); + @IgnoreError public void luaopen_package(@NotNull Pointer luaState); + @IgnoreError public void luaopen_coroutine(@NotNull Pointer luaState); + @IgnoreError public void luaopen_table(@NotNull Pointer luaState); + @IgnoreError public void luaopen_io(@NotNull Pointer luaState); + @IgnoreError public void luaopen_os(@NotNull Pointer luaState); + @IgnoreError public void luaopen_string(@NotNull Pointer luaState); + @IgnoreError public void luaopen_math(@NotNull Pointer luaState); + @IgnoreError public void luaopen_utf8(@NotNull Pointer luaState); + @IgnoreError public void luaopen_debug(@NotNull Pointer luaState); @Nullable + @IgnoreError public Pointer lua_tolstring(@NotNull Pointer luaState, int index, @LongLong long statusCodeReturnPtr); + @IgnoreError public int lua_load(@NotNull Pointer luaState, @LongLong long reader, long userData, @NotNull String chunkName, @NotNull String mode); /** * Pops a table from the stack and sets it as the new metatable for the value at the given index. */ + @IgnoreError public void lua_setmetatable(@NotNull Pointer luaState, int stackIndex); /** * Pushes onto the stack the field e from the metatable of the object at index obj and returns the type of the pushed value. If the object does not have a metatable, or if the metatable does not have this field, pushes nothing and returns LUA_TNIL. */ + @IgnoreError public int luaL_getmetafield(@NotNull Pointer luaState, int stackIndex, @LongLong long stringPtr); public interface lua_CFunction extends Closure { @@ -104,53 +126,81 @@ public interface LuaJNR { // Операции над стаком // загрузка значений из Java на стек + @IgnoreError public void lua_createtable(@NotNull Pointer luaState, int arraySize, int hashSize); + @IgnoreError public void lua_pushnil(@NotNull Pointer luaState); + @IgnoreError public void lua_pushnumber(@NotNull Pointer luaState, double value); + @IgnoreError public void lua_pushinteger(@NotNull Pointer luaState, @LongLong long value); + @IgnoreError public void lua_pushboolean(@NotNull Pointer luaState, int value); + @IgnoreError public void lua_pushglobaltable(@NotNull Pointer luaState); // NUL терминированная строка + @IgnoreError public void lua_pushstring(@NotNull Pointer luaState, @NotNull String value); // двоичная строка + @IgnoreError public long lua_pushlstring(@NotNull Pointer luaState, @LongLong long stringPointer, @LongLong long length); // Загрузка Lua значений на стек + @IgnoreError public int lua_getglobal(@NotNull Pointer luaState, @NotNull String name); // запись значений со стека + @IgnoreError public void lua_settable(@NotNull Pointer luaState, int stackIndex); + @IgnoreError public void lua_setglobal(@NotNull Pointer luaState, @NotNull String name); // проверка стека + @IgnoreError public int lua_checkstack(@NotNull Pointer luaState, int value); + @IgnoreError public int lua_absindex(@NotNull Pointer luaState, int value); + @IgnoreError public int lua_iscfunction(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_isinteger(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_isnumber(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_isstring(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_isuserdata(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_toboolean(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_tocfunction(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_toclose(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_gettable(@NotNull Pointer luaState, int index); @LongLong + @IgnoreError public long lua_tointegerx(@NotNull Pointer luaState, int index, @LongLong long successCode); + @IgnoreError public double lua_tonumberx(@NotNull Pointer luaState, int index, @LongLong long successCode); + @IgnoreError public int lua_settop(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_next(@NotNull Pointer luaState, int index); + @IgnoreError public int lua_type(@NotNull Pointer luaState, int index); /** * Returns the index of the top element in the stack. * Because indices start at 1, this result is equal to the number of elements in the stack; in particular, 0 means an empty stack. */ + @IgnoreError public int lua_gettop(@NotNull Pointer luaState); @NotNull diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 176033f9..17a697ea 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -83,6 +83,7 @@ import ru.dbotthepony.kstarbound.util.ScheduledCoroutineExecutor import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise import ru.dbotthepony.kstarbound.util.random.nextRange import ru.dbotthepony.kstarbound.util.random.random +import ru.dbotthepony.kstarbound.util.supplyAsync import ru.dbotthepony.kstarbound.world.SystemWorldLocation import ru.dbotthepony.kstarbound.world.physics.Poly import java.io.* @@ -99,6 +100,7 @@ import java.util.concurrent.ForkJoinWorkerThread import java.util.concurrent.Future import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Supplier import java.util.random.RandomGenerator import kotlin.NoSuchElementException import kotlin.collections.ArrayList @@ -116,6 +118,12 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca const val DEDUP_CELL_STATES = true const val USE_CAFFEINE_INTERNER = false const val USE_INTERNER = true + + // at most this amount of dynamically loaded json assets will be kept in memory + const val JSON_CACHE_SIZE = 16_000L + // at most this amount of script sources will be kept in memory + const val LUA_SCRIPT_CACHE_SIZE = 4_000L + // enables a fuckton of runtime checks for data which doesn't make much sense // especially for data which will explode legacy client // also changes some constants @@ -288,8 +296,25 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca return scriptCache.computeIfAbsent(path, ::loadScript0) } - fun compileScriptChunk(name: String, chunk: String): ChunkFactory { - return loader.compileTextChunk(name, chunk) + private val luaScriptCache = Caffeine.newBuilder() + .maximumSize(LUA_SCRIPT_CACHE_SIZE) + .expireAfterAccess(Duration.ofHours(1L)) + .scheduler(this) + .executor(EXECUTOR) + .buildAsync(AsyncCacheLoader> { key, executor -> + IO_EXECUTOR.supplyAsync { + val file = locate(key) + + if (!file.isFile) { + KOptional() + } else { + KOptional(file.readToString()) + } + } + }) + + fun readLuaScript(path: String): CompletableFuture { + return luaScriptCache[path].thenApply { it.orThrow { NoSuchElementException("No such file $path") } } } val ELEMENTS_ADAPTER = InternedJsonElementAdapter(STRINGS) @@ -447,7 +472,7 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca } private val jsonAssetsCache = Caffeine.newBuilder() - .maximumSize(4096L) + .maximumSize(JSON_CACHE_SIZE) .expireAfterAccess(Duration.ofMinutes(5L)) .scheduler(this) .executor(EXECUTOR) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt index fe9324e4..6e8ec74b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt @@ -457,6 +457,29 @@ fun TableFactory.tableFrom(collection: Collection): Table { return alloc } +// TODO: error reporting when argument was provided, but it is malformed +// currently, invalid data gets silently discarded, and treated as "no value" aka null +fun LuaThread.getPoly(stackIndex: Int = -1): Poly? { + return readTableValues(stackIndex) { + getVector2d(it) ?: throw IllegalArgumentException("Provided poly table contains invalid points") + }?.let { Poly(it) } +} + +fun LuaThread.ArgStack.getPoly(position: Int = this.position++): Poly { + if (position !in 1 ..this.top) + throw IllegalArgumentException("Bad argument #$position: Poly expected, got nil") + + return lua.getPoly(position) + ?: throw IllegalArgumentException("Bad argument #$position: Poly expected, got ${lua.typeAt(position)}") +} + +fun LuaThread.ArgStack.getPolyOrNull(position: Int = this.position++): Poly? { + if (position !in 1 ..this.top) + return null + + return lua.getPoly(position) +} + fun LuaThread.getLine2d(stackIndex: Int = -1): Line2d? { val abs = this.absStackIndex(stackIndex) @@ -485,7 +508,14 @@ fun LuaThread.ArgStack.getLine2d(position: Int = this.position++): Line2d { throw IllegalArgumentException("Bad argument #$position: Line2d expected, got nil") return lua.getLine2d(position) - ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Line2d expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("Bad argument #$position: Line2d expected, got ${lua.typeAt(position)}") +} + +fun LuaThread.ArgStack.getLine2dOrNull(position: Int = this.position++): Line2d? { + if (position !in 1 ..this.top) + return null + + return lua.getLine2d(position) } fun LuaThread.getVector2d(stackIndex: Int = -1): Vector2d? { @@ -516,7 +546,7 @@ fun LuaThread.ArgStack.getVector2d(position: Int = this.position++): Vector2d { throw IllegalArgumentException("Bad argument #$position: Vector2d expected, got nil") return lua.getVector2d(position) - ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Vector2d expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("Bad argument #$position: Vector2d expected, got ${lua.typeAt(position)}") } fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? { @@ -547,7 +577,171 @@ fun LuaThread.ArgStack.getVector2i(position: Int = this.position++): Vector2i { throw IllegalArgumentException("Bad argument #$position: Vector2i expected, got nil") return lua.getVector2i(position) - ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Vector2i expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("Bad argument #$position: Vector2i expected, got ${lua.typeAt(position)}") +} + +fun LuaThread.ArgStack.getVector2iOrNull(position: Int = this.position++): Vector2i? { + if (position !in 1 ..this.top) + return null + + lua.typeAt(position).isTableOrNothing { "Bad argument #$position: optional Vector2i expected, got nil" } + + return lua.getVector2i(position) +} + +fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? { + val abs = this.absStackIndex(stackIndex) + + if (!this.isTable(abs)) + return null + + push(1) + loadTableValue(abs) + + val x = getLong(abs + 1) + pop() + x ?: return null + + push(2) + loadTableValue(abs) + + val y = getLong(abs + 1) + pop() + y ?: return null + + push(3) + loadTableValue(abs) + + val z = getLong(abs + 1) + pop() + z ?: return null + + push(4) + loadTableValue(abs) + + val w = getLong(abs + 1) ?: 255L + pop() + + return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) +} + +fun LuaThread.ArgStack.getColor(position: Int = this.position++): RGBAColor { + if (position !in 1 ..this.top) + throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got nil") + + return lua.getColor(position) + ?: throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") +} + +fun LuaThread.ArgStack.getColorOrNull(position: Int = this.position++): RGBAColor? { + if (position !in 1 ..this.top) + return null + + return lua.getColor(position) +} + +fun LuaThread.getAABB(stackIndex: Int = -1): AABB? { + val abs = this.absStackIndex(stackIndex) + + if (!this.isTable(abs)) + return null + + push(1) + loadTableValue(abs) + + val x = getDouble(abs + 1) + pop() + x ?: return null + + push(2) + loadTableValue(abs) + + val y = getDouble(abs + 1) + pop() + y ?: return null + + push(3) + loadTableValue(abs) + + val z = getDouble(abs + 1) + pop() + z ?: return null + + push(4) + loadTableValue(abs) + + val w = getDouble(abs + 1) + pop() + w ?: return null + + return AABB(Vector2d(x, y), Vector2d(z, w)) +} + +fun LuaThread.ArgStack.getAABB(position: Int = this.position++): AABB { + if (position !in 1 ..this.top) + throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got nil") + + return lua.getAABB(position) + ?: throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") +} + +fun LuaThread.ArgStack.getAABBOrNull(position: Int = this.position++): AABB? { + if (position !in 1 ..this.top) + return null + + return lua.getAABB(position) +} + +fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? { + val abs = this.absStackIndex(stackIndex) + + if (!this.isTable(abs)) + return null + + push(1) + loadTableValue(abs) + + val x = getLong(abs + 1) + pop() + x ?: return null + + push(2) + loadTableValue(abs) + + val y = getLong(abs + 1) + pop() + y ?: return null + + push(3) + loadTableValue(abs) + + val z = getLong(abs + 1) + pop() + z ?: return null + + push(4) + loadTableValue(abs) + + val w = getLong(abs + 1) + pop() + w ?: return null + + return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt())) +} + +fun LuaThread.ArgStack.getAABBi(position: Int = this.position++): AABBi { + if (position !in 1 ..this.top) + throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got nil") + + return lua.getAABBi(position) + ?: throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") +} + +fun LuaThread.ArgStack.getAABBiOrNull(position: Int = this.position++): AABBi? { + if (position !in 1 ..this.top) + return null + + return lua.getAABBi(position) } fun LuaThread.push(value: IStruct4i) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt index 36682641..511b1363 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt @@ -24,7 +24,6 @@ import java.lang.ref.Cleaner import java.nio.ByteBuffer import java.nio.ByteOrder import kotlin.math.floor -import kotlin.properties.Delegates import kotlin.system.exitProcess @Suppress("unused") @@ -452,6 +451,95 @@ class LuaThread private constructor( return pairs } + fun iterateTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> Unit, valueVisitor: LuaThread.(stackIndex: Int) -> Unit) { + val abs = this.absStackIndex(stackIndex) + + if (!this.isTable(abs)) + return + + this.push() + val top = this.stackTop + + try { + while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { + keyVisitor(this, abs + 1) + valueVisitor(this, abs + 2) + LuaJNR.INSTANCE.lua_settop(this.pointer, top) + } + } finally { + LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1) + } + } + + fun readTableKeys(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> T): MutableList? { + val abs = this.absStackIndex(stackIndex) + + if (!this.isTable(abs)) + return null + + val values = ArrayList() + + this.push() + val top = this.stackTop + + try { + while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { + values.add(keyVisitor(this, abs + 1)) + LuaJNR.INSTANCE.lua_settop(this.pointer, top) + } + } finally { + LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1) + } + + return values + } + + fun readTableValues(stackIndex: Int = -1, valueVisitor: LuaThread.(stackIndex: Int) -> T): MutableList? { + val abs = this.absStackIndex(stackIndex) + + if (!this.isTable(abs)) + return null + + val values = ArrayList() + + this.push() + val top = this.stackTop + + try { + while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { + values.add(valueVisitor(this, abs + 2)) + LuaJNR.INSTANCE.lua_settop(this.pointer, top) + } + } finally { + LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1) + } + + return values + } + + fun readTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> K, valueVisitor: LuaThread.(stackIndex: Int) -> V): MutableList>? { + val abs = this.absStackIndex(stackIndex) + + if (!this.isTable(abs)) + return null + + val values = ArrayList>() + + this.push() + val top = this.stackTop + + try { + while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { + values.add(keyVisitor(this, abs + 1) to valueVisitor(this, abs + 2)) + LuaJNR.INSTANCE.lua_settop(this.pointer, top) + } + } finally { + LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1) + } + + return values + } + fun getTableValue(stackIndex: Int = -2, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? { this.loadTableValue(stackIndex) return this.getJson(limit = limit) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt index a20fe9a1..b3c48466 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt @@ -13,18 +13,143 @@ import ru.dbotthepony.kstarbound.lua.LuaThread.Companion.LUA_TTHREAD import ru.dbotthepony.kstarbound.lua.LuaThread.Companion.LUA_TUSERDATA enum class LuaType { - NONE, - NIL, - BOOLEAN, - LIGHTUSERDATA, - NUMBER, - STRING, - TABLE, + NONE { + override val isNothing: Boolean + get() = true + }, + NIL { + override val isNothing: Boolean + get() = true + }, + BOOLEAN { + override val isBoolean: Boolean + get() = true + }, + LIGHTUSERDATA , + NUMBER { + override val isNumber: Boolean + get() = true + }, + STRING { + override val isString: Boolean + get() = true + }, + TABLE { + override val isTable: Boolean + get() = true + }, FUNCTION, USERDATA, - THREAD, + THREAD { + override val isThread: Boolean + get() = true + }, UMTYPES; + open val isNothing: Boolean + get() = false + + open val isBoolean: Boolean + get() = false + + open val isTable: Boolean + get() = false + + open val isNumber: Boolean + get() = false + + open val isString: Boolean + get() = false + + open val isThread: Boolean + get() = false + + val isBooleanOrNothing: Boolean get() = isBoolean || isNothing + val isTableOrNothing: Boolean get() = isTable || isNothing + val isNumberOrNothing: Boolean get() = isNumber || isNothing + val isStringOrNothing: Boolean get() = isString || isNothing + val isThreadOrNothing: Boolean get() = isThread || isNothing + + inline fun isBooleanOrNothing(formatter: LuaType.() -> String) { + if (!isBooleanOrNothing) { + throw IllegalArgumentException(formatter.invoke(this)) + } + } + + inline fun isTableOrNothing(formatter: LuaType.() -> String) { + if (!isTableOrNothing) { + throw IllegalArgumentException(formatter.invoke(this)) + } + } + + inline fun isNumberOrNothing(formatter: LuaType.() -> String) { + if (!isNumberOrNothing) { + throw IllegalArgumentException(formatter.invoke(this)) + } + } + + inline fun isStringOrNothing(formatter: LuaType.() -> String) { + if (!isStringOrNothing) { + throw IllegalArgumentException(formatter.invoke(this)) + } + } + + inline fun isThreadOrNothing(formatter: LuaType.() -> String) { + if (!isThreadOrNothing) { + throw IllegalArgumentException(formatter.invoke(this)) + } + } + + fun isBooleanOrNothing(expected: String, argumentNumber: Int): Boolean { + if (argumentNumber == -1) { + return isBooleanOrNothing + } else if (!isBooleanOrNothing) { + throw IllegalArgumentException("bad argument $argumentNumber: $expected expected, got $this") + } + + return isBooleanOrNothing + } + + fun isTableOrNothing(expected: String, argumentNumber: Int): Boolean { + if (argumentNumber == -1) { + return isTableOrNothing + } else if (!isTableOrNothing) { + throw IllegalArgumentException("bad argument $argumentNumber: $expected expected, got $this") + } + + return isTableOrNothing + } + + fun isNumberOrNothing(expected: String, argumentNumber: Int): Boolean { + if (argumentNumber == -1) { + return isNumberOrNothing + } else if (!isNumberOrNothing) { + throw IllegalArgumentException("bad argument $argumentNumber: $expected expected, got $this") + } + + return isNumberOrNothing + } + + fun isStringOrNothing(expected: String, argumentNumber: Int): Boolean { + if (argumentNumber == -1) { + return isStringOrNothing + } else if (!isStringOrNothing) { + throw IllegalArgumentException("bad argument $argumentNumber: $expected expected, got $this") + } + + return isStringOrNothing + } + + fun isThreadOrNothing(expected: String, argumentNumber: Int): Boolean { + if (argumentNumber == -1) { + return isThreadOrNothing + } else if (!isThreadOrNothing) { + throw IllegalArgumentException("bad argument $argumentNumber: $expected expected, got $this") + } + + return isThreadOrNothing + } + companion object { fun valueOf(value: Int): LuaType { return when (value) {