From 7a8e366c46358a55c54fa18c59e209244d3e1fc3 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Thu, 23 Feb 2023 09:40:35 +0700 Subject: [PATCH] =?UTF-8?q?=D0=91=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20Lua?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/dbotthepony/kstarbound/lua/LuaJNI.java | 2 +- .../ru/dbotthepony/kstarbound/lua/LuaJNR.java | 4 +- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 31 +--- .../ru/dbotthepony/kstarbound/lua/LuaState.kt | 171 +++++++++++------- test.lua | 2 +- 5 files changed, 118 insertions(+), 92 deletions(-) diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNI.java index f01cb504..5a0d1bd7 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 index b15df21d..063deeed 100644 --- a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java +++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java @@ -13,7 +13,7 @@ import javax.annotation.Nullable; public interface LuaJNR { public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback); public int lua_callk(@NotNull Pointer luaState, int numArgs, int numResults, @LongLong long ctx, @LongLong long callback); - public long lua_atpanic(@NotNull Pointer luaState, long fn); + public long lua_atpanic(@NotNull Pointer luaState, @LongLong long fn); @Nullable public Pointer luaL_newstate(); @@ -75,7 +75,7 @@ public interface LuaJNR { public void lua_pushstring(@NotNull Pointer luaState, @NotNull String value); // двоичная строка - public long lua_pushlstring(@NotNull Pointer luaState, @NotNull Pointer stringPointer, @LongLong long length); + public long lua_pushlstring(@NotNull Pointer luaState, @LongLong long stringPointer, @LongLong long length); // Загрузка Lua значений на стек public int lua_getglobal(@NotNull Pointer luaState, @NotNull String name); diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 720f6f48..d81b8108 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -31,37 +31,18 @@ fun main() { if (true) { val lua = LuaState() - Thread.sleep(5_000L) + Thread.sleep(4_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() + lua.call() lua.loadGlobal("printTable") lua.push(GsonBuilder().create().fromJson(File("playerdata.json").reader(), JsonElement::class.java)) - lua.pcall(1) + lua.call(1) - /*for (t in 1 .. 100) { - lua.loadGlobal("test") - - for (i in 0 until t) - lua.push("sass".repeat(t)) - - lua.pcall(t) - }*/ + lua.loadGlobal("test") + lua.push("s".repeat(10)) + lua.call(1) Thread.sleep(4_000L) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt index 08517f39..fa767ea1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt @@ -9,18 +9,19 @@ import com.kenai.jffi.CallContext import com.kenai.jffi.CallingConvention import com.kenai.jffi.Closure import com.kenai.jffi.ClosureManager +import com.kenai.jffi.MemoryIO import com.kenai.jffi.Type -import com.sun.jna.Native -import com.sun.jna.ptr.LongByReference import jnr.ffi.Memory import jnr.ffi.NativeType -import jnr.ffi.Pointer +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.ref.Cleaner import java.nio.ByteBuffer import java.nio.ByteOrder +import kotlin.system.exitProcess private fun stringToBuffer(str: String): ByteBuffer { val bytes = str.toByteArray(charset = Charsets.UTF_8) @@ -34,17 +35,34 @@ private fun stringToBuffer(str: String): ByteBuffer { class LuaState : Closeable { private val pointer = LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState") + private val cleanable: Cleaner.Cleanable + private val sharedStringBufferPtr = MemoryIO.getInstance().allocateMemory(2L shl 16, false) - private var destroyed = false - private val panicHandler = object : LuaJNR.lua_CFunction { - override fun invoke(luaState: Pointer): Int { - println("$this panicked!") - return 0 + init { + val pointer = pointer + val sharedStringBufferPtr = sharedStringBufferPtr + + cleanable = CLEANER.register(this) { + LuaJNR.INSTANCE.lua_close(pointer) + MemoryIO.getInstance().freeMemory(sharedStringBufferPtr) } } + private val panicHandler = ClosureManager.getInstance().newClosure( + { + LOGGER.fatal("${this@LuaState} at $pointer has panicked! This should be impossible!") + exitProcess(1) + }, + + CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false) + ) + + override fun close() { + cleanable.clean() + } + init { - //LuaJNR.INSTANCE.lua_atpanic(pointer, panicHandler) + LuaJNR.INSTANCE.lua_atpanic(pointer, panicHandler.address) LuaJNR.INSTANCE.luaopen_base(pointer) storeGlobal("_G") @@ -64,26 +82,15 @@ class LuaState : Closeable { storeGlobal("debug") } - fun pushClosure(lambda: (state: LuaState) -> Unit) { - LuaJNI.lua_pushcclosure(pointer.address()) 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 LuaJNR.INSTANCE.lua_checkstack(pointer, minAmount) > 0 + val stackTop: Int get() { + val value = LuaJNR.INSTANCE.lua_gettop(pointer) + check(value >= 0) { "Invalid stack top $value" } + return value } + /** + * Converts the acceptable index idx into an equivalent absolute index (that is, one that does not depend on the stack size). + */ fun absoluteIndex(index: Int): Int { return LuaJNR.INSTANCE.lua_absindex(pointer, index) } @@ -116,17 +123,17 @@ class LuaState : Closeable { closure.dispose() } - fun pcall(numArgs: Int = 0, numResults: Int = 0): Int { + fun call(numArgs: Int = 0, numResults: Int = 0): Int { val status = LuaJNR.INSTANCE.lua_pcallk(pointer, numArgs, numResults, 0, 0L, 0L) if (status == LUA_ERRRUN) { - throw LuaRuntimeException(getString()) + throw LuaRuntimeException(popString()) } return status } - fun getString(index: Int = -1, limit: Long = 4096): String? { + fun popString(index: Int = -1, limit: Long = 2 shl 16): String? { val len = Memory.allocateDirect(LuaJNR.RUNTIME, NativeType.SLONGLONG) val p = LuaJNR.INSTANCE.lua_tolstring(pointer, absoluteIndex(index), len) ?: return null @@ -143,21 +150,6 @@ class LuaState : Closeable { return readBytes.toString(charset = Charsets.UTF_8) } - override fun close() { - if (destroyed) { - throw IllegalStateException("Already destroyed") - } - - LuaJNR.INSTANCE.lua_close(pointer) - destroyed = true - } - - val stackTop: Int get() { - val value = LuaJNR.INSTANCE.lua_gettop(pointer) - check(value >= 0) { "Invalid stack top $value" } - return value - } - fun storeGlobal(name: String) { LuaJNR.INSTANCE.lua_setglobal(pointer, name) } @@ -166,6 +158,22 @@ class LuaState : Closeable { LuaJNR.INSTANCE.lua_getglobal(pointer, name) } + fun push(closure: (state: LuaState) -> Unit) { + LuaJNI.lua_pushcclosure(pointer.address()) lazy@{ + try { + closure.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 push() { LuaJNR.INSTANCE.lua_pushnil(pointer) } @@ -178,23 +186,53 @@ class LuaState : Closeable { LuaJNR.INSTANCE.lua_pushinteger(pointer, value) } + fun push(value: Double) { + LuaJNR.INSTANCE.lua_pushnumber(pointer, value) + } + + fun push(value: Float) { + LuaJNR.INSTANCE.lua_pushnumber(pointer, value.toDouble()) + } + + fun push(value: Boolean) { + LuaJNR.INSTANCE.lua_pushboolean(pointer, if (value) 1 else 0) + } + fun push(value: String) { val bytes = value.toByteArray(Charsets.UTF_8) - val block = LuaJNR.RUNTIME.memoryManager.allocateDirect(bytes.size) ?: throw OutOfMemoryError("Unable to allocate ${bytes.size} bytes on heap") + if (bytes.size < 2 shl 16) { + MemoryIO.getInstance().putByteArray(sharedStringBufferPtr, bytes, 0, bytes.size) + LuaJNR.INSTANCE.lua_pushlstring(pointer, sharedStringBufferPtr, bytes.size.toLong()) + } else { + val mem = MemoryIO.getInstance() + val block = mem.allocateMemory(bytes.size.toLong(), false) - //try { - block.put(0L, bytes, 0, bytes.size) - LuaJNR.INSTANCE.lua_pushlstring(pointer, block, bytes.size.toLong()) - //} finally { - // Memory.allocate() - //} + if (block == 0L) + throw OutOfMemoryError("Unable to allocate ${bytes.size} bytes on heap") + + try { + mem.putByteArray(block, bytes, 0, bytes.size) + LuaJNR.INSTANCE.lua_pushlstring(pointer, block, bytes.size.toLong()) + } finally { + mem.freeMemory(block) + } + } + } + + fun pushTable(arraySize: Int = 0, hashSize: Int = 0): Int { + LuaJNR.INSTANCE.lua_createtable(pointer, arraySize, hashSize) + return stackTop + } + + fun setTableValue(stackIndex: Int) { + LuaJNR.INSTANCE.lua_settable(pointer, stackIndex) } fun push(value: JsonElement) { when (value) { JsonNull.INSTANCE -> { - LuaJNR.INSTANCE.lua_pushnil(pointer) + push() } is JsonPrimitive -> { @@ -202,39 +240,37 @@ class LuaState : Closeable { val num = value.asNumber when (num) { - is Int, is Long -> LuaJNR.INSTANCE.lua_pushinteger(pointer, num.toLong()) - else -> LuaJNR.INSTANCE.lua_pushnumber(pointer, num.toDouble()) + is Int, is Long -> push(num.toLong()) + else -> push(num.toDouble()) } } else if (value.isString) { push(value.asString) } else if (value.isBoolean) { - LuaJNR.INSTANCE.lua_pushboolean(pointer, if (value.asBoolean) 1 else 0) + push(value.asBoolean) } else { throw IllegalArgumentException(value.toString()) } } is JsonArray -> { - LuaJNR.INSTANCE.lua_createtable(pointer, value.size(), 0) - val index = stackTop + val index = pushTable(arraySize = value.size()) for ((i, v) in value.withIndex()) { - LuaJNR.INSTANCE.lua_pushinteger(pointer, i.toLong() + 1L) + push(i + 1L) push(v) - LuaJNR.INSTANCE.lua_settable(pointer, index) + setTableValue(index) } } is JsonObject -> { - LuaJNR.INSTANCE.lua_createtable(pointer, 0, value.size()) - val index = stackTop + val index = pushTable(hashSize = value.size()) for ((k, v) in value.entrySet()) { push(k) push(v) - LuaJNR.INSTANCE.lua_settable(pointer, index) + setTableValue(index) } } @@ -243,4 +279,13 @@ class LuaState : Closeable { } } } + + companion object { + private val LOGGER = LogManager.getLogger() + private val CLEANER = Cleaner.create { + val thread = Thread(it, "LuaState cleaner") + thread.priority = 1 + thread + } + } } diff --git a/test.lua b/test.lua index 3312a8b1..b93ee9aa 100644 --- a/test.lua +++ b/test.lua @@ -16,7 +16,7 @@ function printTable(input) end function test(...) - print('Called test with ' .. select('#', ...) .. ' arguments: ' .. table.concat({...}, ', ')) + print('Called test with ' .. select('#', ...) .. ' arguments: ' .. #table.concat({...}, ', ')) end print(collectgarbage('count') * 1024)