From 645766ed42c30ea77f2dfa3ae69a4b0fb0fb10c4 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 22 Feb 2023 19:32:34 +0700 Subject: [PATCH] =?UTF-8?q?=D0=BE=D0=BD=D0=BE=20=D0=BA=D1=80=D0=B0=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 5 +- .../ru/dbotthepony/kstarbound/lua/LuaJNA.java | 91 +++++-- .../ru/dbotthepony/kstarbound/lua/LuaJNI.java | 2 +- .../ru/dbotthepony/kstarbound/lua/LuaJNR.java | 75 ++++++ .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 42 +++ .../ru/dbotthepony/kstarbound/lua/Lua.kt | 193 -------------- .../ru/dbotthepony/kstarbound/lua/LuaState.kt | 249 ++++++++++++++++++ .../kstarbound/util/RenderDirectives.kt | 15 ++ test.lua | 24 ++ 9 files changed, 477 insertions(+), 219 deletions(-) create mode 100644 src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/lua/Lua.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/RenderDirectives.kt create mode 100644 test.lua diff --git a/build.gradle.kts b/build.gradle.kts index 9d6c8a4f..10153738 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ application { mainClass.set("ru.dbotthepony.kstarbound.MainKt") } -java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) +java.toolchain.languageVersion.set(JavaLanguageVersion.of(20)) tasks.compileKotlin { kotlinOptions { @@ -77,7 +77,8 @@ dependencies { runtimeOnly("org.lwjgl", "lwjgl-par", classifier = lwjglNatives) runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives) - implementation("net.java.dev.jna:jna:5.10.0") + implementation("net.java.dev.jna:jna:5.13.0") + implementation("com.github.jnr:jnr-ffi:2.2.13") implementation("ru.dbotthepony:kbox2d:2.4.1.+") implementation("ru.dbotthepony:kvector:1.3.2") diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNA.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNA.java index 22f75e5b..33d2e562 100644 --- a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNA.java +++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNA.java @@ -1,39 +1,84 @@ package ru.dbotthepony.kstarbound.lua; +import com.sun.jna.Callback; +import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.ptr.LongByReference; +import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; import java.io.File; -public final class LuaJNA { - public static native int lua_pcallk(long p, int numArgs, int numResults, int msgh, Pointer ctx, LuaJNADynamic.lua_KFunction callback); - public static native int lua_callk(long p, int numArgs, int numResults, Pointer ctx, LuaJNADynamic.lua_KFunction callback); +@SuppressWarnings({"UnnecessaryModifier", "SpellCheckingInspection", "unused"}) +public interface LuaJNA extends Library { + public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, Pointer ctx, lua_KFunction callback); + public int lua_callk(@NotNull Pointer luaState, int numArgs, int numResults, Pointer ctx, lua_KFunction callback); + public Pointer lua_atpanic(@NotNull Pointer luaState, @NotNull lua_CFunction fn); - public static native Pointer luaL_newstate(); - public static native void lua_close(long pointer); + @Nullable + public Pointer luaL_newstate(); + public void lua_close(@NotNull Pointer luaState); // Стандартные библиотеки - public static native void luaopen_base(long pointer); - public static native void luaopen_package(long pointer); - public static native void luaopen_coroutine(long pointer); - public static native void luaopen_table(long pointer); - public static native void luaopen_io(long pointer); - public static native void luaopen_os(long pointer); - public static native void luaopen_string(long pointer); - public static native void luaopen_math(long pointer); - public static native void luaopen_utf8(long pointer); - public static native void luaopen_debug(long pointer); + public void luaopen_base(@NotNull Pointer luaState); + public void luaopen_package(@NotNull Pointer luaState); + public void luaopen_coroutine(@NotNull Pointer luaState); + public void luaopen_table(@NotNull Pointer luaState); + public void luaopen_io(@NotNull Pointer luaState); + public void luaopen_os(@NotNull Pointer luaState); + public void luaopen_string(@NotNull Pointer luaState); + public void luaopen_math(@NotNull Pointer luaState); + public void luaopen_utf8(@NotNull Pointer luaState); + public void luaopen_debug(@NotNull Pointer luaState); - public static native int lua_checkstack(long pointer, int value); - public static native int lua_absindex(long pointer, int value); - public static native int lua_gettop(long pointer); + public Pointer lua_tolstring(@NotNull Pointer luaState, int index, @NotNull LongByReference size); - public static native Pointer lua_tolstring(long pointer, int index, LongByReference size); + public int lua_load(@NotNull Pointer luaState, @NotNull lua_Reader reader, @Nullable Pointer userData, @NotNull String chunkName, @NotNull String mode); - public static native void lua_setglobal(long pointer, String name); - - static { - Native.register(new File("./lua54.dll").getAbsolutePath()); + public interface lua_CFunction extends Callback { + int invoke(@NotNull Pointer luaState); } + + public interface lua_Reader extends Callback { + @Nullable + Pointer readNextChunk(@NotNull Pointer luaState, @Nullable Pointer userData, @NotNull LongByReference sizeToRead); + } + + public interface lua_KFunction extends Callback { + int invoke(@NotNull Pointer luaState, int status, Pointer context); + } + + // Операции над стаком + // загрузка значений из Java на стек + public void lua_createtable(@NotNull Pointer luaState, int arraySize, int hashSize); + public void lua_pushnil(@NotNull Pointer luaState); + public void lua_pushnumber(@NotNull Pointer luaState, double value); + public void lua_pushinteger(@NotNull Pointer luaState, long value); + public void lua_pushboolean(@NotNull Pointer luaState, int value); + + // NUL терминированная строка + public void lua_pushstring(@NotNull Pointer luaState, @NotNull String value); + + // двоичная строка + @Nullable + public Pointer lua_pushlstring(@NotNull Pointer luaState, long stringPointer, long length); + + // Загрузка Lua значений на стек + public int lua_getglobal(@NotNull Pointer luaState, @NotNull String name); + + // запись значений со стека + public void lua_settable(@NotNull Pointer luaState, int stackIndex); + public void lua_setglobal(@NotNull Pointer luaState, @NotNull String name); + + public int lua_checkstack(@NotNull Pointer luaState, int value); + public int lua_absindex(@NotNull Pointer luaState, int value); + + /** + * 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. + */ + public int lua_gettop(@NotNull Pointer luaState); + + public static final LuaJNA INSTANCE = Native.load("lua54", LuaJNA.class); } diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java index 5a0d1bd7..f01cb504 100644 --- a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java +++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java @@ -17,6 +17,6 @@ public final class LuaJNI { } static { - System.load(new File("lua_glue.dll").getAbsolutePath()); + //System.load(new File("lua_glue.dll").getAbsolutePath()); } } diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java new file mode 100644 index 00000000..3f739f42 --- /dev/null +++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java @@ -0,0 +1,75 @@ +package ru.dbotthepony.kstarbound.lua; + +import jnr.ffi.LibraryLoader; +import jnr.ffi.Pointer; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings({"UnnecessaryModifier", "SpellCheckingInspection", "unused"}) +public interface LuaJNR { + public int lua_pcallk(long luaState, int numArgs, int numResults, int msgh, long ctx, LuaJNA.lua_KFunction callback); + public int lua_callk(long luaState, int numArgs, int numResults, long ctx, LuaJNA.lua_KFunction callback); + public long lua_atpanic(long luaState, @NotNull LuaJNA.lua_CFunction fn); + + public long luaL_newstate(); + public void lua_close(long luaState); + + // Стандартные библиотеки + public void luaopen_base(long luaState); + public void luaopen_package(long luaState); + public void luaopen_coroutine(long luaState); + public void luaopen_table(long luaState); + public void luaopen_io(long luaState); + public void luaopen_os(long luaState); + public void luaopen_string(long luaState); + public void luaopen_math(long luaState); + public void luaopen_utf8(long luaState); + public void luaopen_debug(long luaState); + + public long lua_tolstring(long luaState, int index, @NotNull Pointer size); + + public int lua_load(long luaState, @NotNull lua_Reader reader, long userData, @NotNull String chunkName, @NotNull String mode); + + public interface lua_CFunction { + int invoke(long luaState); + } + + public interface lua_Reader { + public long readNextChunk(long luaState, long userData, @NotNull Pointer sizeToRead); + } + + public interface lua_KFunction { + public int invoke(long luaState, int status, long context); + } + + // Операции над стаком + // загрузка значений из Java на стек + public void lua_createtable(long luaState, int arraySize, int hashSize); + public void lua_pushnil(long luaState); + public void lua_pushnumber(long luaState, double value); + public void lua_pushinteger(long luaState, long value); + public void lua_pushboolean(long luaState, int value); + + // NUL терминированная строка + public void lua_pushstring(long luaState, @NotNull String value); + + // двоичная строка + public long lua_pushlstring(long luaState, long stringPointer, long length); + + // Загрузка Lua значений на стек + public int lua_getglobal(long luaState, @NotNull String name); + + // запись значений со стека + public void lua_settable(long luaState, int stackIndex); + public void lua_setglobal(long luaState, @NotNull String name); + + public int lua_checkstack(long luaState, int value); + public int lua_absindex(long luaState, int value); + + /** + * 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. + */ + public int lua_gettop(long luaState); + + public static final LuaJNR INSTANCE = LibraryLoader.create(LuaJNR.class).load("lua54"); +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 544e206b..5335b3c3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -1,7 +1,11 @@ package ru.dbotthepony.kstarbound +import com.google.gson.GsonBuilder +import com.google.gson.JsonElement +import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonPrimitive +import com.sun.jna.Native import org.apache.logging.log4j.LogManager import org.lwjgl.Version import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose @@ -10,6 +14,7 @@ import ru.dbotthepony.kstarbound.client.render.Animator import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kstarbound.defs.item.DynamicItemDefinition import ru.dbotthepony.kstarbound.io.BTreeDB +import ru.dbotthepony.kstarbound.lua.LuaState import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.entities.ItemEntity import ru.dbotthepony.kstarbound.world.entities.PlayerEntity @@ -17,11 +22,48 @@ import ru.dbotthepony.kvector.vector.ndouble.Vector2d import java.io.ByteArrayInputStream import java.io.DataInputStream import java.io.File +import java.util.Random import java.util.zip.Inflater private val LOGGER = LogManager.getLogger() fun main() { + if (true) { + val lua = LuaState() + + //Thread.sleep(5_000L) + + lua.load(File("test.lua").readText()) + //lua.load("print('hello world!', ...)") + + //lua.push(GsonBuilder().create().fromJson(File("playerdata.json").reader(), JsonElement::class.java)) + + /*lua.push(JsonObject().also { + it.add("Сыр", JsonPrimitive("Гиршок")) + it.add("сас", JsonPrimitive("сос")) + it.add("сыс", JsonPrimitive(4)) + it.add("ы", JsonNull.INSTANCE) + it.add("s", JsonObject().also { + it.add("Вложенный", JsonPrimitive("Объект!")) + }) + })*/ + + lua.pcall() + + for (t in 1 .. 100) { + lua.loadGlobal("test") + + for (i in 0 until t) + lua.push("sass".repeat(t)) + + lua.pcall(t) + } + + Thread.sleep(4_000L) + + return + } + val starbound = Starbound() LOGGER.info("Running LWJGL ${Version.getVersion()}") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Lua.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Lua.kt deleted file mode 100644 index e0b5166b..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Lua.kt +++ /dev/null @@ -1,193 +0,0 @@ -package ru.dbotthepony.kstarbound.lua - -import com.sun.jna.Callback -import com.sun.jna.Library -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.ptr.LongByReference -import org.apache.logging.log4j.LogManager -import org.lwjgl.system.MemoryUtil -import java.io.Closeable -import java.io.PrintWriter -import java.io.StringWriter -import java.lang.RuntimeException -import java.nio.ByteBuffer -import java.nio.ByteOrder - -private typealias size_t = Long -private typealias psize_t = LongByReference - -private typealias lua_KContext = Pointer - -interface LuaJNADynamic : Library { - fun lua_atpanic(luaState: Pointer, fn: lua_CFunction): Pointer - fun lua_load(luaState: Pointer, reader: lua_Reader, data: Pointer?, chunkName: String, mode: String): Int - fun lua_pushlstring(luaState: Pointer, str: Pointer, len: size_t): Pointer? - - interface lua_CFunction : Callback { - fun invoke(luaState: Pointer): Int - } - - interface lua_Reader : Callback { - fun invoke(luaState: Pointer, data: Pointer?, size: psize_t): Pointer? /* String */ - } - - interface lua_KFunction : Callback { - /* - ** Type for continuation functions - */ - fun invoke(luaState: Pointer, status: Int, ctx: lua_KContext?): Int - } -} - -val LUA_JNA: LuaJNADynamic = Native.load("lua54", LuaJNADynamic::class.java) - -private fun stringToBuffer(str: String): ByteBuffer { - val bytes = str.toByteArray(charset = Charsets.UTF_8) - val buf = ByteBuffer.allocateDirect(bytes.size) - buf.order(ByteOrder.nativeOrder()) - bytes.forEach(buf::put) - buf.position(0) - - return buf -} - -private val SHARED_BUFFER = ByteBuffer.allocateDirect(16384).also { it.order(ByteOrder.nativeOrder()) } -private val SHARED_BUFFER_PTR = Pointer(MemoryUtil.memAddress(SHARED_BUFFER)) - -private fun loadString(string: String): Int { - SHARED_BUFFER.position(0) - val bytes = string.toByteArray(charset = Charsets.UTF_8) - bytes.forEach(SHARED_BUFFER::put) - SHARED_BUFFER.position(0) - return bytes.size -} - -private fun lua_pushlstring(luaState: Pointer, str: String) { - val len = loadString(str) - LUA_JNA.lua_pushlstring(luaState, SHARED_BUFFER_PTR, len.toLong()) -} - -class LuaState : Closeable { - private val pointer = LuaJNA.luaL_newstate() - private val nativePointer = Pointer.nativeValue(pointer) - - private var destroyed = false - private val panicHandler = object : LuaJNADynamic.lua_CFunction { - override fun invoke(luaState: Pointer): Int { - throw RuntimeException("$this panicked!") - } - } - - init { - if (pointer == Pointer.NULL) - throw OutOfMemoryError("Unable to allocate new LuaState") - - LUA_JNA.lua_atpanic(pointer, panicHandler) - - LuaJNA.luaopen_base(nativePointer) - LuaJNA.luaopen_package(nativePointer) - LuaJNA.luaopen_table(nativePointer) - LuaJNA.luaopen_coroutine(nativePointer) - LuaJNA.luaopen_string(nativePointer) - LuaJNA.luaopen_math(nativePointer) - LuaJNA.luaopen_utf8(nativePointer) - LuaJNA.luaopen_debug(nativePointer) - - pushClosure { - val build = mutableListOf() - - for (i in 1 .. stackTop) { - build.add(getString(i)) - } - - LOGGER.info("Lua/print(): {}", build.joinToString("\t")) - } - - LuaJNA.lua_setglobal(nativePointer, "print") - } - - fun pushClosure(lambda: (state: LuaState) -> Unit) { - LuaJNI.lua_pushcclosure(nativePointer) lazy@{ - try { - lambda.invoke(this@LuaState) - } catch (err: Throwable) { - val builder = StringWriter() - val printWriter = PrintWriter(builder) - err.printStackTrace(printWriter) - lua_pushlstring(pointer, builder.toString()) - return@lazy 1 - } - - return@lazy 0 - } - } - - fun checkStack(minAmount: Int): Boolean { - return LuaJNA.lua_checkstack(nativePointer, minAmount) > 0 - } - - fun absoluteIndex(index: Int): Int { - return LuaJNA.lua_absindex(nativePointer, index) - } - - fun load(code: String, chunkName: String = "main chunk") { - val buf = stringToBuffer(code) - - throwLoadError(LUA_JNA.lua_load(pointer, object : LuaJNADynamic.lua_Reader { - override fun invoke(luaState: Pointer, data: Pointer?, size: psize_t): Pointer? { - if (buf.remaining() == 0) { - size.value = 0 - return Pointer.NULL - } - - size.value = buf.remaining().toLong() - val p = Pointer(MemoryUtil.memAddress(buf)) - buf.position(buf.remaining()) - return p - } - }, Pointer.NULL, chunkName, "t")) - } - - fun pcall(numArgs: Int = 0, numResults: Int = 0): Int { - val status = LuaJNA.lua_pcallk(nativePointer, numArgs, numResults, 0, null, null) - - if (status == LUA_ERRRUN) { - throw LuaRuntimeException(getString()) - } - - return status - } - - fun getString(index: Int = -1, limit: Long = 4096): String? { - val len = psize_t() - val p = LuaJNA.lua_tolstring(nativePointer, absoluteIndex(index), len) ?: return null - - if (len.value == 0L) { - return "" - } - - if (len.value >= limit) { - throw IllegalStateException("Unreasonably long Lua string: ${len.value}") - } - - val readBytes = ByteArray(len.value.toInt()) - p.read(0L, readBytes, 0, readBytes.size) - return readBytes.toString(charset = Charsets.UTF_8) - } - - val stackTop get() = LuaJNA.lua_gettop(nativePointer) - - override fun close() { - if (destroyed) { - throw IllegalStateException("Already destroyed") - } - - LuaJNA.lua_close(nativePointer) - destroyed = true - } - - companion object { - private val LOGGER = LogManager.getLogger(LuaState::class.java) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt new file mode 100644 index 00000000..732ae20b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt @@ -0,0 +1,249 @@ +package ru.dbotthepony.kstarbound.lua + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.ptr.LongByReference +import org.apache.logging.log4j.LogManager +import org.lwjgl.system.MemoryUtil +import java.io.Closeable +import java.io.PrintWriter +import java.io.StringWriter +import java.lang.RuntimeException +import java.nio.ByteBuffer +import java.nio.ByteOrder + +private fun stringToBuffer(str: String): ByteBuffer { + val bytes = str.toByteArray(charset = Charsets.UTF_8) + val buf = ByteBuffer.allocateDirect(bytes.size) + buf.order(ByteOrder.nativeOrder()) + bytes.forEach(buf::put) + buf.position(0) + + return buf +} + +class LuaState : Closeable { + private val pointer = LuaJNA.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState") + private val nativePointer = Pointer.nativeValue(pointer) + + private var destroyed = false + private val panicHandler = object : LuaJNA.lua_CFunction { + override fun invoke(luaState: Pointer): Int { + println("$this panicked!") + return 0 + } + } + + init { + LuaJNA.INSTANCE.lua_atpanic(pointer, panicHandler) + + LuaJNA.INSTANCE.luaopen_base(pointer) + storeGlobal("_G") + LuaJNA.INSTANCE.luaopen_package(pointer) + storeGlobal("package") + LuaJNA.INSTANCE.luaopen_table(pointer) + storeGlobal("table") + LuaJNA.INSTANCE.luaopen_coroutine(pointer) + storeGlobal("coroutine") + LuaJNA.INSTANCE.luaopen_string(pointer) + storeGlobal("string") + LuaJNA.INSTANCE.luaopen_math(pointer) + storeGlobal("math") + LuaJNA.INSTANCE.luaopen_utf8(pointer) + storeGlobal("utf8") + LuaJNA.INSTANCE.luaopen_debug(pointer) + storeGlobal("debug") + + /* + pushClosure { + val build = mutableListOf() + + for (i in 1 .. stackTop) { + build.add(getString(i)) + } + + LOGGER.info("Lua/print(): {}", build.joinToString("\t")) + } + + LuaJNA.lua_setglobal(pointer, "print") + + */ + } + + fun pushClosure(lambda: (state: LuaState) -> Unit) { + LuaJNI.lua_pushcclosure(nativePointer) lazy@{ + try { + lambda.invoke(this@LuaState) + } catch (err: Throwable) { + val builder = StringWriter() + val printWriter = PrintWriter(builder) + err.printStackTrace(printWriter) + push(builder.toString()) + return@lazy 1 + } + + return@lazy 0 + } + } + + fun checkStack(minAmount: Int): Boolean { + return LuaJNA.INSTANCE.lua_checkstack(pointer, minAmount) > 0 + } + + fun absoluteIndex(index: Int): Int { + return LuaJNA.INSTANCE.lua_absindex(pointer, index) + } + + fun load(code: String, chunkName: String = "main chunk") { + val buf = stringToBuffer(code) + + throwLoadError(LuaJNA.INSTANCE.lua_load(pointer, object : LuaJNA.lua_Reader { + override fun readNextChunk(luaState: Pointer, userdata: Pointer?, size: LongByReference): Pointer? { + if (buf.remaining() == 0) { + size.value = 0 + return Pointer.NULL + } + + size.value = buf.remaining().toLong() + val p = MemoryUtil.memAddress(buf) + buf.position(buf.remaining()) + return Pointer(p) + } + }, Pointer.NULL, chunkName, "t")) + } + + fun pcall(numArgs: Int = 0, numResults: Int = 0): Int { + val status = LuaJNA.INSTANCE.lua_pcallk(pointer, numArgs, numResults, 0, null, null) + + if (status == LUA_ERRRUN) { + throw LuaRuntimeException(getString()) + } + + return status + } + + fun getString(index: Int = -1, limit: Long = 4096): String? { + val len = LongByReference() + val p = LuaJNA.INSTANCE.lua_tolstring(pointer, absoluteIndex(index), len) ?: return null + + if (len.value == 0L) { + return "" + } + + if (len.value >= limit) { + throw IllegalStateException("Unreasonably long Lua string: ${len.value}") + } + + val readBytes = ByteArray(len.value.toInt()) + p.read(0L, readBytes, 0, readBytes.size) + return readBytes.toString(charset = Charsets.UTF_8) + } + + override fun close() { + if (destroyed) { + throw IllegalStateException("Already destroyed") + } + + LuaJNA.INSTANCE.lua_close(pointer) + destroyed = true + } + + val stackTop: Int get() { + val value = LuaJNA.INSTANCE.lua_gettop(pointer) + check(value >= 0) { "Invalid stack top $value" } + return value + } + + fun storeGlobal(name: String) { + LuaJNA.INSTANCE.lua_setglobal(pointer, name) + } + + fun loadGlobal(name: String) { + LuaJNA.INSTANCE.lua_getglobal(pointer, name) + } + + fun push() { + LuaJNA.INSTANCE.lua_pushnil(pointer) + } + + fun push(value: Int) { + LuaJNA.INSTANCE.lua_pushinteger(pointer, value.toLong()) + } + + fun push(value: Long) { + LuaJNA.INSTANCE.lua_pushinteger(pointer, value) + } + + fun push(value: String) { + val bytes = value.toByteArray(Charsets.UTF_8) + val block = Native.malloc(bytes.size.toLong()) + + if (block == 0L) + throw OutOfMemoryError("Unable to allocate ${bytes.size} bytes on heap") + + try { + Pointer(block).write(0L, bytes, 0, bytes.size) + LuaJNA.INSTANCE.lua_pushlstring(pointer, block, bytes.size.toLong()) + } finally { + Native.free(block) + } + } + + fun push(value: JsonElement) { + when (value) { + JsonNull.INSTANCE -> { + LuaJNA.INSTANCE.lua_pushnil(pointer) + } + + is JsonPrimitive -> { + if (value.isNumber) { + val num = value.asNumber + + when (num) { + is Int, is Long -> LuaJNA.INSTANCE.lua_pushinteger(pointer, num.toLong()) + else -> LuaJNA.INSTANCE.lua_pushnumber(pointer, num.toDouble()) + } + } else if (value.isString) { + push(value.asString) + } else if (value.isBoolean) { + LuaJNA.INSTANCE.lua_pushboolean(pointer, if (value.asBoolean) 1 else 0) + } else { + throw IllegalArgumentException(value.toString()) + } + } + + is JsonArray -> { + LuaJNA.INSTANCE.lua_createtable(pointer, value.size(), 0) + val index = stackTop + + for ((i, v) in value.withIndex()) { + LuaJNA.INSTANCE.lua_pushinteger(pointer, i.toLong() + 1L) + push(v) + + LuaJNA.INSTANCE.lua_settable(pointer, index) + } + } + + is JsonObject -> { + LuaJNA.INSTANCE.lua_createtable(pointer, 0, value.size()) + val index = stackTop + + for ((k, v) in value.entrySet()) { + push(k) + push(v) + + LuaJNA.INSTANCE.lua_settable(pointer, index) + } + } + + else -> { + throw IllegalArgumentException(value.toString()) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/RenderDirectives.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/RenderDirectives.kt new file mode 100644 index 00000000..1d5ba833 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/RenderDirectives.kt @@ -0,0 +1,15 @@ +package ru.dbotthepony.kstarbound.util + +class RenderDirectives(val raw: String) { + override fun equals(other: Any?): Boolean { + return super.equals(other) + } + + override fun hashCode(): Int { + return super.hashCode() + } + + override fun toString(): String { + return "RenderDirectives[$raw]" + } +} diff --git a/test.lua b/test.lua new file mode 100644 index 00000000..3312a8b1 --- /dev/null +++ b/test.lua @@ -0,0 +1,24 @@ + +function printTable(input) + if type(input) ~= 'table' then + print(input) + return + end + + for k, v in pairs(input) do + if type(v) == 'table' then + print(k .. ' = ') + printTable(v) + else + print(k .. ' = ', v, type(v)) + end + end +end + +function test(...) + print('Called test with ' .. select('#', ...) .. ' arguments: ' .. table.concat({...}, ', ')) +end + +print(collectgarbage('count') * 1024) +--printTable(select(1, ...)) +--print('hello!')