KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt

1131 lines
34 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.lua
import com.github.benmanes.caffeine.cache.Interner
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.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 it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import jnr.ffi.Pointer
import org.apache.logging.log4j.LogManager
import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.api.IStruct3i
import ru.dbotthepony.kvector.api.IStruct4i
import ru.dbotthepony.kvector.vector.Vector2i
import java.io.Closeable
import java.lang.ref.Cleaner
import java.lang.ref.WeakReference
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.system.exitProcess
@Suppress("unused")
class LuaState private constructor(private val pointer: Pointer, val stringInterner: Interner<String> = Starbound.strings) : Closeable {
constructor(stringInterner: Interner<String> = Starbound.strings) : this(LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState"), stringInterner) {
val pointer = this.pointer
val panic = ClosureManager.getInstance().newClosure(
{
LOGGER.fatal("Engine Error: LuaState at $pointer has panicked!")
exitProcess(1)
},
CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false)
)
this.cleanable = CLEANER.register(this) {
LuaJNR.INSTANCE.lua_close(pointer)
panic.dispose()
}
panic.setAutoRelease(false)
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
LuaJNR.INSTANCE.luaopen_base(this.pointer)
this.storeGlobal("_G")
LuaJNR.INSTANCE.luaopen_table(this.pointer)
this.storeGlobal("table")
LuaJNR.INSTANCE.luaopen_coroutine(this.pointer)
this.storeGlobal("coroutine")
LuaJNR.INSTANCE.luaopen_string(this.pointer)
this.storeGlobal("string")
LuaJNR.INSTANCE.luaopen_math(this.pointer)
this.storeGlobal("math")
LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
this.storeGlobal("utf8")
LuaJNR.INSTANCE.luaopen_debug(this.pointer)
this.storeGlobal("debug")
}
private val thread = Thread.currentThread()
private var cleanable: Cleaner.Cleanable? = null
override fun close() {
this.cleanable?.clean()
}
val stackTop: Int get() {
val value = LuaJNR.INSTANCE.lua_gettop(this.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 absStackIndex(index: Int): Int {
if (index >= 0)
return index
return LuaJNR.INSTANCE.lua_absindex(this.pointer, index)
}
private fun throwLoadError(code: Int) {
when (code) {
LUA_OK -> {}
LUA_ERRSYNTAX -> throw InvalidLuaSyntaxException(this.popString())
LUA_ERRMEM -> throw LuaMemoryAllocException()
// LUA_ERRGCMM -> throw LuaGCException()
else -> throw LuaException("Unknown Lua Loading error: $code")
}
}
fun load(code: String, chunkName: String = "main chunk") {
val bytes = code.toByteArray(charset = Charsets.UTF_8)
val buf = ByteBuffer.allocateDirect(bytes.size)
buf.order(ByteOrder.nativeOrder())
bytes.forEach(buf::put)
buf.position(0)
val closure = ClosureManager.getInstance().newClosure(
object : Closure {
override fun invoke(buffer: Closure.Buffer) {
val amountToRead = LuaJNR.RUNTIME.memoryManager.newPointer(buffer.getAddress(2))
if (buf.remaining() == 0) {
amountToRead.putLong(0L, 0L)
buffer.setAddressReturn(0L)
return
}
amountToRead.putLongLong(0L, buf.remaining().toLong())
val p = MemoryUtil.memAddress(buf)
buf.position(buf.remaining())
buffer.setAddressReturn(p)
}
},
CallContext.getCallContext(Type.POINTER, arrayOf(Type.POINTER, Type.ULONG_LONG, Type.POINTER), CallingConvention.DEFAULT, false)
)
this.throwLoadError(LuaJNR.INSTANCE.lua_load(this.pointer, closure.address, 0L, chunkName, "t"))
closure.dispose()
}
fun call(numArgs: Int = 0, numResults: Int = 0): Int {
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
if (status == LUA_ERRRUN) {
throw LuaRuntimeException(this.getString())
}
return status
}
fun getString(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? {
if (!this.isString(stackIndex))
return null
return getStringRaw(stackIndex, limit)
}
private fun getStringRaw(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? {
check(limit <= Int.MAX_VALUE) { "Can't allocate string bigger than ${Int.MAX_VALUE} characters" }
val stack = MemoryStack.stackPush()
val status = stack.mallocLong(1)
val p = LuaJNR.INSTANCE.lua_tolstring(this.pointer, this.absStackIndex(stackIndex), MemoryUtil.memAddress(status)) ?: return null
val len = status[0]
stack.close()
if (len == 0L)
return ""
else if (len >= limit)
throw IllegalStateException("Unreasonably long Lua string: $len")
val readBytes = ByteArray(len.toInt())
p.get(0L, readBytes, 0, readBytes.size)
return this.stringInterner.intern(readBytes.toString(charset = Charsets.UTF_8))
}
fun isCFunction(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_iscfunction(this.pointer, this.absStackIndex(stackIndex)) > 0
fun isFunction(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.FUNCTION
fun isInteger(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isinteger(this.pointer, this.absStackIndex(stackIndex)) > 0
fun isLightUserdata(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.LIGHTUSERDATA
fun isNil(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.NIL
fun isNone(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.NONE
fun isNoneOrNil(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex).let { it == LuaType.NIL || it == LuaType.NONE }
fun isNumber(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isnumber(this.pointer, this.absStackIndex(stackIndex)) > 0
fun isString(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isstring(this.pointer, this.absStackIndex(stackIndex)) > 0
fun isTable(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.TABLE
fun isThread(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.THREAD
fun isUserdata(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isuserdata(this.pointer, this.absStackIndex(stackIndex)) > 0
fun isBoolean(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.BOOLEAN
fun getBoolean(stackIndex: Int = -1): Boolean? {
if (!this.isBoolean(stackIndex))
return null
return LuaJNR.INSTANCE.lua_toboolean(this.pointer, stackIndex) > 0
}
private fun getBooleanRaw(stackIndex: Int = -1): Boolean {
return LuaJNR.INSTANCE.lua_toboolean(this.pointer, stackIndex) > 0
}
fun getLong(stackIndex: Int = -1): Long? {
if (!this.isNumber(stackIndex))
return null
val stack = MemoryStack.stackPush()
val status = stack.mallocInt(1)
val value = LuaJNR.INSTANCE.lua_tointegerx(this.pointer, stackIndex, MemoryUtil.memAddress(status))
val b = status[0] > 0
stack.close()
if (!b)
return null
return value
}
private fun getLongRaw(stackIndex: Int = -1): Long {
val stack = MemoryStack.stackPush()
val status = stack.mallocInt(1)
val value = LuaJNR.INSTANCE.lua_tointegerx(this.pointer, stackIndex, MemoryUtil.memAddress(status))
val b = status[0] > 0
stack.close()
if (!b) throw NumberFormatException("Lua was unable to parse Long present on stack at ${this.absStackIndex(stackIndex)} ($stackIndex)")
return value
}
fun getDouble(stackIndex: Int = -1): Double? {
if (!this.isNumber(stackIndex))
return null
val stack = MemoryStack.stackPush()
val status = stack.mallocInt(1)
val value = LuaJNR.INSTANCE.lua_tonumberx(this.pointer, stackIndex, MemoryUtil.memAddress(status))
val b = status[0] > 0
stack.close()
if (!b)
return null
return value
}
private fun getDoubleRaw(stackIndex: Int = -1): Double {
val stack = MemoryStack.stackPush()
val status = stack.mallocInt(1)
val value = LuaJNR.INSTANCE.lua_tonumberx(this.pointer, stackIndex, MemoryUtil.memAddress(status))
val b = status[0] > 0
stack.close()
if (!b) throw NumberFormatException("Lua was unable to parse Double present on stack at ${this.absStackIndex(stackIndex)} ($stackIndex)")
return value
}
fun typeAt(stackIndex: Int = -1): LuaType {
return when (val value = LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex)) {
LUA_TNONE -> LuaType.NONE
LUA_TNIL -> LuaType.NIL
LUA_TBOOLEAN -> LuaType.BOOLEAN
LUA_TLIGHTUSERDATA -> LuaType.LIGHTUSERDATA
LUA_TNUMBER -> LuaType.NUMBER
LUA_TSTRING -> LuaType.STRING
LUA_TTABLE -> LuaType.TABLE
LUA_TFUNCTION -> LuaType.FUNCTION
LUA_TUSERDATA -> LuaType.USERDATA
LUA_TTHREAD -> LuaType.THREAD
LUA_NUMTYPES -> LuaType.UMTYPES
else -> throw RuntimeException("Invalid Lua type: $value")
}
}
fun getValue(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
val abs = this.absStackIndex(stackIndex)
if (abs == 0)
return null
return when (this.typeAt(abs)) {
LuaType.NONE -> null
LuaType.NIL -> null // JsonNull.INSTANCE
LuaType.BOOLEAN -> InternedJsonElementAdapter.of(this.getBooleanRaw(abs))
LuaType.LIGHTUSERDATA -> throw IllegalArgumentException("Can not get light userdata from Lua stack at $abs")
LuaType.NUMBER -> JsonPrimitive(if (this.isInteger(abs)) this.getLongRaw(abs) else this.getDoubleRaw(abs))
LuaType.STRING -> JsonPrimitive(this.getStringRaw(abs, limit = limit))
LuaType.TABLE -> this.getTableRaw(abs)
LuaType.FUNCTION -> throw IllegalArgumentException("Can not get function from Lua stack at $abs")
LuaType.USERDATA -> throw IllegalArgumentException("Can not get userdata from Lua stack at $abs")
LuaType.THREAD -> throw IllegalArgumentException("Can not get thread from Lua stack at $abs")
LuaType.UMTYPES -> throw IllegalArgumentException("Can not get umtypes from Lua stack at $abs")
}
}
fun getTable(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): JsonObject? {
val abs = this.absStackIndex(stackIndex)
if (!this.isTable(abs))
return null
return getTableRaw(abs, limit)
}
private fun getTableRaw(abs: Int, limit: Long = DEFAULT_STRING_LIMIT): JsonObject {
val pairs = JsonObject()
this.push()
val top = this.stackTop
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
val key = this.getValue(abs + 1, limit = limit)
val value = this.getValue(abs + 2, limit = limit)
if (key is JsonPrimitive && value != null) {
pairs.add(this.stringInterner.intern(key.asString), value)
}
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
}
return pairs
}
fun getVector2i(stackIndex: Int = -1): Vector2i? {
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
return Vector2i(x.toInt(), y.toInt())
}
/**
* Пропуски заполняются [JsonNull.INSTANCE]
*
* Не числовые индексы игнорируются
*/
fun getArray(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): JsonArray? {
val abs = this.absStackIndex(stackIndex)
if (!this.isTable(abs))
return null
val pairs = Int2ObjectAVLTreeMap<JsonElement>()
this.push()
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
val key = this.getValue(abs + 1, limit = limit)
val value = this.getValue(abs + 2, limit = limit)
if (key is JsonPrimitive && key.isNumber && value != null) {
pairs.put(key.asInt, value)
}
this.pop()
}
val result = JsonArray()
for ((index, value) in pairs) {
while (index > result.size()) {
result.add(JsonNull.INSTANCE)
}
result.add(value)
}
return result
}
fun getTableValue(stackIndex: Int = -2, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
this.loadTableValue(stackIndex)
return this.getValue(limit = limit)
}
fun loadTableValue(stackIndex: Int = -2, allowNothing: Boolean = false) {
val abs = this.absStackIndex(stackIndex)
if (!this.isTable(abs))
throw IllegalArgumentException("Attempt to index an ${this.typeAt(abs)} value")
if (LuaJNR.INSTANCE.lua_gettable(this.pointer, abs) == LUA_TNONE && !allowNothing)
throw IllegalStateException("loaded TNONE from Lua table")
}
fun loadTableValue(name: String, stackIndex: Int = -2) {
this.push(name)
this.loadTableValue(stackIndex)
}
fun popBoolean(): Boolean? {
try {
return this.getBoolean()
} finally {
this.pop()
}
}
fun popLong(): Long? {
try {
return this.getLong()
} finally {
this.pop()
}
}
fun popDouble(): Double? {
try {
return this.getDouble()
} finally {
this.pop()
}
}
fun popValue(): JsonElement? {
try {
return this.getValue()
} finally {
this.pop()
}
}
fun popTable(): JsonObject? {
try {
return this.getTable()
} finally {
this.pop()
}
}
fun popString(limit: Long = DEFAULT_STRING_LIMIT): String? {
try {
return this.getString(limit = limit)
} finally {
this.pop()
}
}
fun pop(amount: Int = 1): Int {
if (amount == 0) return 0
check(amount > 0) { "Invalid amount to pop: $amount" }
val old = this.stackTop
val new = (old - amount).coerceAtLeast(0)
LuaJNR.INSTANCE.lua_settop(this.pointer, new)
return old - new
}
fun storeGlobal(name: String) {
LuaJNR.INSTANCE.lua_setglobal(this.pointer, name)
}
fun loadGlobal(name: String) {
LuaJNR.INSTANCE.lua_getglobal(this.pointer, name)
}
inner class ArgStack(val top: Int) {
val lua get() = this@LuaState
var position = 1
fun hasSomethingAt(position: Int): Boolean {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.typeAt(position) != LuaType.NONE
}
fun hasSomethingAt(): Boolean {
if (hasSomethingAt(this.position + 1)) {
return true
}
this.position++
return false
}
fun isStringAt(position: Int = this.position): Boolean {
return this@LuaState.typeAt(position) == LuaType.STRING
}
fun isNumberAt(position: Int = this.position): Boolean {
return this@LuaState.typeAt(position) == LuaType.NUMBER
}
fun getString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.getString(position, limit = limit)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: string expected, got ${this@LuaState.typeAt(position)}")
}
fun getLong(position: Int = this.position++): Long {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.getLong(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: long expected, got ${this@LuaState.typeAt(position)}")
}
fun getStringOrNil(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String? {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
val type = this@LuaState.typeAt(position)
if (type != LuaType.STRING && type != LuaType.NIL)
throw IllegalArgumentException("Lua code error: Bad argument #$position: string expected, got $type")
return this@LuaState.getString(position, limit = limit)
}
fun getStringOrNull(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String? {
if (position > this.top)
return null
return this.getStringOrNil(position, limit = limit)
}
fun getValue(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
val value = this@LuaState.getValue(position, limit = limit)
return value ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: anything expected, got ${this@LuaState.typeAt(position)}")
}
fun getTable(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonObject {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
val value = this@LuaState.getTable(position, limit = limit)
return value ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: table expected, got ${this@LuaState.typeAt(position)}")
}
fun getArray(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonArray {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
val value = this@LuaState.getArray(position, limit = limit)
return value ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: table expected, got ${this@LuaState.typeAt(position)}")
}
fun getAnything(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
check(position in 1..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.getValue(position, limit = limit)
}
fun getDoubleOrNil(position: Int = this.position++): Double? {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
val type = this@LuaState.typeAt(position)
if (type != LuaType.NUMBER && type != LuaType.NIL)
throw IllegalArgumentException("Lua code error: Bad argument #$position: double expected, got $type")
return this@LuaState.getDouble(position)
}
fun getDouble(position: Int = this.position++): Double {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.getDouble(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: double expected, got ${this@LuaState.typeAt(position)}")
}
fun getInt(position: Int = this.position++): Int {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.getLong(position)?.toInt()
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: integer expected, got ${this@LuaState.typeAt(position)}")
}
fun getVector2i(position: Int = this.position++): Vector2i {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.getVector2i(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Vector2i expected, got ${this@LuaState.typeAt(position)}")
}
fun getBoolOrNil(position: Int = this.position++): Boolean? {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
val type = this@LuaState.typeAt(position)
if (type != LuaType.BOOLEAN && type != LuaType.NIL)
throw IllegalArgumentException("Lua code error: Bad argument #$position: boolean expected, got $type")
return this@LuaState.getBoolean(position)
}
fun getBool(position: Int = this.position++): Boolean {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" }
return this@LuaState.getBoolean(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: boolean expected, got ${this@LuaState.typeAt(position)}")
}
fun getBoolOrNull(position: Int = this.position++): Boolean? {
if (position > this.top) return null
return this.getBoolOrNil(position)
}
fun push() = this@LuaState.push()
fun push(value: Int) = this@LuaState.push(value)
fun push(value: Long) = this@LuaState.push(value)
fun push(value: Double) = this@LuaState.push(value)
fun push(value: Float) = this@LuaState.push(value)
fun push(value: Boolean) = this@LuaState.push(value)
fun push(value: String?) = this@LuaState.push(value)
fun push(value: JsonElement?) = this@LuaState.push(value)
fun push(value: RegistryObject<*>?) = this@LuaState.push(value)
fun pushFull(value: RegistryObject<*>?) = this@LuaState.pushFull(value)
}
/**
* Создаёт новое замыкание на стороне Lua. [function], будучи переданным в Lua,
* создаст новый **GC Root**.
*
* Вышестоящий код ОБЯЗАН использовать [ArgStack] и его [ArgStack.lua] для доступа к [LuaState], так как
* при вызове замыкания из Lua текущий [LuaState] может отличаться от того, которому был передан [function].
*/
fun push(function: ArgStack.() -> Int, performanceCritical: Boolean) {
val weak = WeakReference(this)
val pointer = this.pointer
LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
var realLuaState = weak.get()
if (realLuaState == null || realLuaState.pointer.address() != it) {
if (realLuaState == null)
realLuaState = LuaState(LuaJNR.RUNTIME.memoryManager.newPointer(it))
else
realLuaState = LuaState(LuaJNR.RUNTIME.memoryManager.newPointer(it), stringInterner = realLuaState.stringInterner)
}
val args = realLuaState.ArgStack(realLuaState.stackTop)
val rememberStack: ArrayList<String>?
if (performanceCritical) {
rememberStack = null
} else {
rememberStack = ArrayList(Exception().stackTraceToString().split('\n'))
rememberStack.removeAt(0) // java.lang. ...
// rememberStack.removeAt(0) // at ... push( ... )
}
try {
val value = function.invoke(args)
check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" }
return@lazy value
} catch (err: Throwable) {
try {
if (performanceCritical) {
realLuaState.push(err.stackTraceToString())
return@lazy -1
} else {
rememberStack!!
val newStack = err.stackTraceToString().split('\n').toMutableList()
val rememberIterator = rememberStack.listIterator(rememberStack.size)
val iterator = newStack.listIterator(newStack.size)
var hit = false
while (rememberIterator.hasPrevious() && iterator.hasPrevious()) {
val a = rememberIterator.previous()
val b = iterator.previous()
if (a == b) {
hit = true
iterator.remove()
} else {
break
}
}
if (hit) {
newStack[newStack.size - 1] = "\t<...>"
}
realLuaState.push(newStack.joinToString("\n"))
return@lazy -1
}
} catch(err2: Throwable) {
realLuaState.push("JVM suffered an exception while handling earlier exception: ${err2.stackTraceToString()}; earlier: ${err.stackTraceToString()}")
return@lazy -1
}
}
}
}
/**
* Создаёт новое замыкание на стороне Lua. [function], будучи переданным в Lua,
* создаст новый **GC Root**, что может создать утечку память если будет создана циклическая зависимость,
* если быть неаккуратным.
*
* Пример: Parent -> Lua -> Closure -> Parent
*
* В примере выше Lua никогда не будет собран сборщиком мусора, тем самым никогда не будет (автоматически) вызван [close]
*
* Во избежание данной ситуации предоставлена функция [pushWeak]
*
* @see CClosure.invoke
*/
fun push(function: ArgStack.() -> Int) = this.push(function, !RECORD_STACK_TRACES)
/**
* Создаёт новое замыкание на стороне Lua. [function], будучи переданным в Lua,
* создаст новый **GC Root**, что может создать утечку память если будет создана циклическая зависимость,
* если быть неаккуратным.
*
* В отличие от обычного [push] для замыканий, данный вариант создаёт [WeakReference] на [self],
* который, в свою очередь, может ссылаться обратно на данный [LuaState] через свои структуры.
*
* В силу того, что замыкание более не может создать циклическую ссылку на данный [LuaState] через [self], сборщик
* мусора сможет удалить [self], а затем удалить [LuaState].
*
*/
fun <T : Any> pushWeak(self: T, function: T.(args: ArgStack) -> Int, performanceCritical: Boolean) {
val weakSelf = WeakReference(self)
return push(performanceCritical = performanceCritical, function = lazy@{
@Suppress("name_shadowing")
val self = weakSelf.get()
if (self == null) {
this.lua.push("Referenced 'this' got reclaimed by JVM GC")
return@lazy -1
}
function.invoke(self, this)
})
}
/**
* Создаёт новое замыкание на стороне Lua. [function], будучи переданным в Lua,
* создаст новый **GC Root**, что может создать утечку память если будет создана циклическая зависимость,
* если быть неаккуратным.
*
* В отличие от обычного [push] для замыканий, данный вариант создаёт [WeakReference] на [self],
* который, в свою очередь, может ссылаться обратно на данный [LuaState] через свои структуры.
*
* В силу того, что замыкание более не может создать циклическую ссылку на данный [LuaState] через [self], сборщик
* мусора сможет удалить [self], а затем удалить [LuaState].
*
*/
fun <T : Any> pushWeak(self: T, function: T.(args: ArgStack) -> Int) = this.pushWeak(self, function, !RECORD_STACK_TRACES)
fun push() {
LuaJNR.INSTANCE.lua_pushnil(this.pointer)
}
fun push(value: Int) {
LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value.toLong())
}
fun push(value: Long) {
LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value)
}
fun push(value: Double) {
LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value)
}
fun push(value: Float) {
LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value.toDouble())
}
fun push(value: Boolean) {
LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0)
}
fun push(value: IStruct4i) {
pushTable(arraySize = 4)
val table = stackTop
val (x, y, z, w) = value
push(1)
push(x)
setTableValue(table)
push(2)
push(y)
setTableValue(table)
push(3)
push(z)
setTableValue(table)
push(4)
push(w)
setTableValue(table)
}
fun push(value: IStruct3i) {
pushTable(arraySize = 3)
val table = stackTop
val (x, y, z) = value
push(1)
push(x)
setTableValue(table)
push(2)
push(y)
setTableValue(table)
push(3)
push(z)
setTableValue(table)
}
fun push(value: IStruct2i) {
pushTable(arraySize = 2)
val table = stackTop
val (x, y) = value
push(1)
push(x)
setTableValue(table)
push(2)
push(y)
setTableValue(table)
}
fun pushStrings(strings: Iterable<String?>) {
val index = this.pushTable(arraySize = if (strings is Collection) strings.size else 0)
for ((i, v) in strings.withIndex()) {
this.push(i + 1L)
this.push(v)
this.setTableValue(index)
}
}
fun pushStrings(strings: Iterator<String?>) {
val index = this.pushTable()
for ((i, v) in strings.withIndex()) {
this.push(i + 1L)
this.push(v)
this.setTableValue(index)
}
}
fun push(value: String?) {
if (value == null) {
this.push()
return
}
val bytes = value.toByteArray(Charsets.UTF_8)
if (bytes.size < 2 shl 16) {
MemoryIO.getInstance().putByteArray(sharedStringBufferPtr, bytes, 0, bytes.size)
LuaJNR.INSTANCE.lua_pushlstring(this.pointer, sharedStringBufferPtr, bytes.size.toLong())
} else {
val mem = MemoryIO.getInstance()
val block = mem.allocateMemory(bytes.size.toLong(), false)
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(this.pointer, block, bytes.size.toLong())
} finally {
mem.freeMemory(block)
}
}
}
fun pushTable(arraySize: Int = 0, hashSize: Int = 0): Int {
LuaJNR.INSTANCE.lua_createtable(this.pointer, arraySize, hashSize)
return this.stackTop
}
fun setTableValue(stackIndex: Int) {
LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex)
}
fun <T : Any> setTableFunction(key: String, self: T, value: T.(args: ArgStack) -> Int) {
val table = this.stackTop
this.push(key)
this.pushWeak(self, value)
this.setTableValue(table)
}
fun <T : Any> setTableClosure(key: String, self: T, value: T.(args: ArgStack) -> Unit) {
val table = this.stackTop
this.push(key)
this.pushWeak(self) { value.invoke(this, it); 0 }
this.setTableValue(table)
}
fun setTableValue(key: String, value: JsonElement?) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: String, value: Int) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: String, value: Long) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: String, value: String) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: String, value: Float) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: String, value: Double) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Int, value: JsonElement?) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Int, value: Int) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Int, value: Long) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Int, value: String) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Int, value: Float) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Int, value: Double) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Long, value: JsonElement?) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Long, value: Int) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Long, value: Long) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Long, value: String) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Long, value: Float) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun setTableValue(key: Long, value: Double) {
val table = this.stackTop
this.push(key)
this.push(value)
this.setTableValue(table)
}
fun push(value: JsonElement?) {
when (value) {
null, JsonNull.INSTANCE -> {
this.push()
}
is JsonPrimitive -> {
if (value.isNumber) {
val num = value.asNumber
when (num) {
is Int, is Long -> this.push(num.toLong())
else -> this.push(num.toDouble())
}
} else if (value.isString) {
this.push(value.asString)
} else if (value.isBoolean) {
this.push(value.asBoolean)
} else {
throw IllegalArgumentException(value.toString())
}
}
is JsonArray -> {
val index = this.pushTable(arraySize = value.size())
for ((i, v) in value.withIndex()) {
this.push(i + 1L)
this.push(v)
this.setTableValue(index)
}
}
is JsonObject -> {
val index = this.pushTable(hashSize = value.size())
for ((k, v) in value.entrySet()) {
this.push(k)
this.push(v)
this.setTableValue(index)
}
}
else -> {
throw IllegalArgumentException(value.toString())
}
}
}
fun push(value: RegistryObject<*>?) {
if (value == null)
push()
else
push(value.toJson())
}
fun pushFull(value: RegistryObject<*>?) {
if (value == null)
push()
else {
pushTable(hashSize = 2)
setTableValue("path", value.file.computeFullPath())
setTableValue("config", value.toJson())
}
}
companion object {
private val LOGGER = LogManager.getLogger()
private val CLEANER: Cleaner = Cleaner.create {
val thread = Thread(it, "Lua State Cleaner")
thread.priority = 1
thread
}
private val sharedBuffers = ThreadLocal<Long>()
private val sharedStringBufferPtr: Long get() {
var p: Long? = sharedBuffers.get()
if (p == null) {
p = MemoryIO.getInstance().allocateMemory(DEFAULT_STRING_LIMIT, false)
if (p == 0L) {
throw OutOfMemoryError("Unable to allocate new string shared buffer")
}
sharedBuffers.set(p)
val p2 = p
CLEANER.register(Thread.currentThread()) {
MemoryIO.getInstance().freeMemory(p2)
}
}
return p
}
const val LUA_TNONE = -1
const val LUA_TNIL = 0
const val LUA_TBOOLEAN = 1
const val LUA_TLIGHTUSERDATA = 2
const val LUA_TNUMBER = 3
const val LUA_TSTRING = 4
const val LUA_TTABLE = 5
const val LUA_TFUNCTION = 6
const val LUA_TUSERDATA = 7
const val LUA_TTHREAD = 8
const val LUA_NUMTYPES = 9
const val CHUNK_READ_SIZE = 2L shl 10
const val DEFAULT_STRING_LIMIT = 2L shl 16
const val RECORD_STACK_TRACES = false
}
}