1131 lines
34 KiB
Kotlin
1131 lines
34 KiB
Kotlin
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
|
||
}
|
||
}
|