1313 lines
36 KiB
Kotlin
1313 lines
36 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 jnr.ffi.Pointer
|
|
import org.apache.logging.log4j.LogManager
|
|
import org.lwjgl.system.MemoryStack
|
|
import org.lwjgl.system.MemoryUtil
|
|
import ru.dbotthepony.kommons.gson.set
|
|
import ru.dbotthepony.kommons.util.Delegate
|
|
import ru.dbotthepony.kommons.util.KOptional
|
|
import ru.dbotthepony.kstarbound.Starbound
|
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
|
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
|
import ru.dbotthepony.kstarbound.lua.bindings.provideRootBindings
|
|
import ru.dbotthepony.kstarbound.lua.bindings.provideUtilityBindings
|
|
import ru.dbotthepony.kstarbound.util.random.random
|
|
import java.io.Closeable
|
|
import java.lang.ref.Cleaner
|
|
import java.nio.ByteBuffer
|
|
import java.nio.ByteOrder
|
|
import java.util.random.RandomGenerator
|
|
import kotlin.math.floor
|
|
import kotlin.properties.Delegates
|
|
import kotlin.system.exitProcess
|
|
|
|
@Suppress("unused")
|
|
class LuaThread 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 mem address $pointer has panicked!")
|
|
exitProcess(1)
|
|
},
|
|
|
|
CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false)
|
|
)
|
|
|
|
this.cleanable = Starbound.CLEANER.register(this) {
|
|
LuaJNR.INSTANCE.lua_close(pointer)
|
|
panic.dispose()
|
|
}
|
|
|
|
panic.setAutoRelease(false)
|
|
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
|
|
|
|
randomHolder = Delegate.Box(random())
|
|
|
|
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")
|
|
|
|
provideUtilityBindings(this)
|
|
provideRootBindings(this)
|
|
|
|
load(globalScript, "@starbound.jar!/scripts/global.lua")
|
|
call()
|
|
}
|
|
|
|
fun interface Fn {
|
|
fun invoke(args: ArgStack): Int
|
|
}
|
|
|
|
private var cleanable: Cleaner.Cleanable? = null
|
|
private var randomHolder: Delegate<RandomGenerator> by Delegates.notNull()
|
|
|
|
/**
|
|
* Responsible for generating random numbers using math.random
|
|
*
|
|
* Can be safely set to any other random number generator;
|
|
* math.randomseed sets this property to brand new generator with required seed
|
|
*/
|
|
var random: RandomGenerator
|
|
get() = randomHolder.get()
|
|
set(value) = randomHolder.accept(value)
|
|
|
|
private fun initializeFrom(other: LuaThread) {
|
|
randomHolder = other.randomHolder
|
|
}
|
|
|
|
fun newThread(): LuaThread {
|
|
val pointer = LuaJNR.INSTANCE.lua_newthread(pointer)
|
|
|
|
return LuaThread(pointer, stringInterner).also {
|
|
it.initializeFrom(this)
|
|
}
|
|
}
|
|
|
|
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)
|
|
)
|
|
|
|
throwLoadError(LuaJNR.INSTANCE.lua_load(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
|
|
}
|
|
|
|
/**
|
|
* Returns boolean indicating whenever function exists
|
|
*/
|
|
inline fun invokeGlobal(name: String, arguments: LuaThread.() -> Int): Boolean {
|
|
val top = stackTop
|
|
|
|
try {
|
|
val type = loadGlobal(name)
|
|
if (type != LuaType.FUNCTION)
|
|
return false
|
|
|
|
val numArguments = arguments(this)
|
|
check(numArguments >= 0) { "Invalid amount of arguments provided to Lua function" }
|
|
call(numArguments)
|
|
return true
|
|
} finally {
|
|
setTop(top)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns empty [KOptional] if function does not exist
|
|
*/
|
|
inline fun <T> invokeGlobal(name: String, numResults: Int, arguments: LuaThread.() -> Int, results: LuaThread.(firstValue: Int) -> T): KOptional<T> {
|
|
require(numResults > 0) { "Invalid amount of results: $numResults" }
|
|
val top = stackTop
|
|
|
|
try {
|
|
val type = loadGlobal(name)
|
|
if (type != LuaType.FUNCTION)
|
|
return KOptional()
|
|
|
|
val numArguments = arguments(this)
|
|
call(numArguments, numResults)
|
|
return KOptional(results(this, top + 1))
|
|
} finally {
|
|
setTop(top)
|
|
}
|
|
}
|
|
|
|
inline fun <T> eval(chunk: String, name: String = "eval", numResults: Int, arguments: LuaThread.() -> Int, results: LuaThread.(firstValue: Int) -> T): T {
|
|
require(numResults > 0) { "Invalid amount of results: $numResults" }
|
|
|
|
val top = stackTop
|
|
|
|
try {
|
|
val numArguments = arguments(this)
|
|
load(chunk, name)
|
|
call(numArguments, numResults)
|
|
return results(this, top + 1)
|
|
} finally {
|
|
setTop(top)
|
|
}
|
|
}
|
|
|
|
inline fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Int) {
|
|
val top = stackTop
|
|
|
|
try {
|
|
val numArguments = arguments(this)
|
|
load(chunk, name)
|
|
call(numArguments, 0)
|
|
} finally {
|
|
setTop(top)
|
|
}
|
|
}
|
|
|
|
fun eval(chunk: String, name: String = "eval") {
|
|
val top = stackTop
|
|
|
|
try {
|
|
load(chunk, name)
|
|
call()
|
|
} finally {
|
|
setTop(top)
|
|
}
|
|
}
|
|
|
|
private val attachedScripts = ArrayList<String>()
|
|
private var initCalled = false
|
|
|
|
fun initScripts(callInit: Boolean = true): Boolean {
|
|
check(!initCalled) { "Already initialized scripts!" }
|
|
initCalled = true
|
|
|
|
if (attachedScripts.isEmpty()) {
|
|
return true
|
|
}
|
|
|
|
val loadScripts = attachedScripts.map { Starbound.readLuaScript(it) to it }
|
|
attachedScripts.clear()
|
|
|
|
try {
|
|
// minor hiccups during unpopulated script cache should be tolerable
|
|
for ((chunk, path) in loadScripts) {
|
|
load(chunk.join(), "@$path")
|
|
call()
|
|
}
|
|
} catch (err: Exception) {
|
|
LOGGER.error("Failed to attach scripts to Lua environment", err)
|
|
return false
|
|
}
|
|
|
|
try {
|
|
if (callInit) {
|
|
val type = loadGlobal("init")
|
|
|
|
if (type == LuaType.FUNCTION) {
|
|
call()
|
|
} else if (type == LuaType.NIL || type == LuaType.NONE) {
|
|
pop()
|
|
} else {
|
|
pop()
|
|
throw LuaRuntimeException("init is not a function: $type")
|
|
}
|
|
}
|
|
} catch (err: Exception) {
|
|
LOGGER.error("Failed to call init() in Lua environment", err)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
fun attach(script: AssetPath) {
|
|
attach(script.fullPath)
|
|
}
|
|
|
|
fun attach(script: String) {
|
|
if (initCalled) {
|
|
// minor hiccups during unpopulated script cache should be tolerable
|
|
load(Starbound.readLuaScript(script).join(), "@$script")
|
|
call()
|
|
} else {
|
|
attachedScripts.add(script)
|
|
}
|
|
}
|
|
|
|
fun attach(script: Collection<AssetPath>) {
|
|
script.forEach { attach(it) }
|
|
}
|
|
|
|
@JvmName("attachAsStrings")
|
|
fun attach(script: Collection<String>) {
|
|
script.forEach { attach(it) }
|
|
}
|
|
|
|
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? {
|
|
require(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))
|
|
return 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 LuaType.valueOf(LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex))
|
|
}
|
|
|
|
/**
|
|
* Loads value at specified stack's position as Json value according to next rules:
|
|
* * [LuaType.NONE] and invalid stack positions are loaded as literal `null`
|
|
* * [LuaType.NIL] is loaded as [JsonNull]
|
|
* * [LuaType.BOOLEAN], [LuaType.STRING] and [LuaType.NUMBER] are loaded as [JsonPrimitive]
|
|
* * [LuaType.TABLE] gets special treatment, if created by `jarray()` global Lua function AND it has only whole numeric indices, then it gets loaded as [JsonArray]. Otherwise (including being created by `jobject()`) it gets loaded as [JsonObject], non-string indices get cast to string.
|
|
* * Everything else generates [IllegalArgumentException]
|
|
*/
|
|
fun getJson(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
|
|
val abs = this.absStackIndex(stackIndex)
|
|
|
|
if (abs == 0)
|
|
return null
|
|
|
|
return when (val type = this.typeAt(abs)) {
|
|
LuaType.NONE -> null
|
|
LuaType.NIL -> JsonNull.INSTANCE
|
|
LuaType.BOOLEAN -> InternedJsonElementAdapter.of(this.getBooleanRaw(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 -> {
|
|
val values = HashMap<Any, JsonElement>()
|
|
|
|
val hintType = LuaJNR.INSTANCE.luaL_getmetafield(pointer, abs, __typehint)
|
|
var hint = LUA_HINT_NONE
|
|
var hasNonIntegerIndices = false
|
|
|
|
if (hintType == LUA_TNUMBER) {
|
|
hint = getLongRaw().toInt()
|
|
pop()
|
|
|
|
// if there is a valid hint, then try to look for __nils
|
|
val nilsType = LuaJNR.INSTANCE.luaL_getmetafield(pointer, abs, __nils)
|
|
|
|
if (nilsType == LUA_TTABLE) {
|
|
// good.
|
|
push()
|
|
val top = this.stackTop
|
|
|
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, top - 1) != 0) {
|
|
val value = this.getJson(top + 1, limit = limit)
|
|
|
|
if (value is JsonPrimitive) {
|
|
if (value.isString) {
|
|
values[value.asString] = JsonNull.INSTANCE
|
|
hasNonIntegerIndices = true
|
|
} else if (value.isNumber) {
|
|
var v = value.asNumber
|
|
|
|
if (v is Long || v is Double && floor(v) == v) {
|
|
v = v.toLong()
|
|
} else {
|
|
hasNonIntegerIndices = true
|
|
}
|
|
|
|
values[v] = JsonNull.INSTANCE
|
|
}
|
|
|
|
}
|
|
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
|
}
|
|
|
|
pop()
|
|
} else if (nilsType != LUA_TNIL) {
|
|
// what a shame.
|
|
pop()
|
|
}
|
|
} else if (hintType != LUA_TNIL) {
|
|
pop()
|
|
}
|
|
|
|
if (hint != LUA_HINT_OBJECT && hint != LUA_HINT_ARRAY) {
|
|
hint = LUA_HINT_NONE
|
|
}
|
|
|
|
push()
|
|
val top = this.stackTop
|
|
|
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, top - 1) != 0) {
|
|
val key = this.getJson(top, limit = limit)
|
|
val value = this.getJson(top + 1, limit = limit)
|
|
|
|
if (key is JsonPrimitive && value != null) {
|
|
if (key.isString) {
|
|
values[key.asString] = value
|
|
hasNonIntegerIndices = true
|
|
} else if (key.isNumber) {
|
|
var v = key.asNumber
|
|
|
|
if (v is Long || v is Double && floor(v) == v) {
|
|
v = v.toLong()
|
|
} else {
|
|
hasNonIntegerIndices = true
|
|
}
|
|
|
|
values[v] = value
|
|
}
|
|
}
|
|
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
|
}
|
|
|
|
val interpretAsList: Boolean
|
|
|
|
when (hint) {
|
|
LUA_HINT_NONE -> interpretAsList = !hasNonIntegerIndices && values.isNotEmpty()
|
|
LUA_HINT_OBJECT -> interpretAsList = false
|
|
LUA_HINT_ARRAY -> interpretAsList = !hasNonIntegerIndices
|
|
else -> throw RuntimeException()
|
|
}
|
|
|
|
if (interpretAsList) {
|
|
val list = JsonArray()
|
|
val sorted = LongArray(values.size)
|
|
var i = 0
|
|
values.keys.forEach { sorted[i++] = it as Long }
|
|
sorted.sort()
|
|
|
|
for (key in sorted) {
|
|
while (list.size() < key - 1) {
|
|
list.add(JsonNull.INSTANCE)
|
|
}
|
|
|
|
list.add(values[key])
|
|
}
|
|
|
|
return list
|
|
} else {
|
|
val obj = JsonObject()
|
|
|
|
for ((k, v) in values) {
|
|
obj[k.toString()] = v
|
|
}
|
|
|
|
return obj
|
|
}
|
|
}
|
|
|
|
//else -> throw IllegalArgumentException("Can not get $type from Lua stack at $abs")
|
|
else -> return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forcefully loads stack's value as key-value table
|
|
*/
|
|
fun getTable(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): JsonObject? {
|
|
val abs = this.absStackIndex(stackIndex)
|
|
|
|
if (!this.isTable(abs))
|
|
return null
|
|
|
|
val pairs = JsonObject()
|
|
this.push()
|
|
val top = this.stackTop
|
|
|
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
|
val key = this.getJson(abs + 1, limit = limit)
|
|
val value = this.getJson(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 iterateTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> Unit, valueVisitor: LuaThread.(stackIndex: Int) -> Unit) {
|
|
val abs = this.absStackIndex(stackIndex)
|
|
|
|
if (!this.isTable(abs))
|
|
return
|
|
|
|
this.push()
|
|
val top = this.stackTop
|
|
|
|
try {
|
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
|
keyVisitor(this, abs + 1)
|
|
valueVisitor(this, abs + 2)
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
|
}
|
|
} finally {
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1)
|
|
}
|
|
}
|
|
|
|
fun <T> readTableKeys(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> T): MutableList<T>? {
|
|
val abs = this.absStackIndex(stackIndex)
|
|
|
|
if (!this.isTable(abs))
|
|
return null
|
|
|
|
val values = ArrayList<T>()
|
|
|
|
this.push()
|
|
val top = this.stackTop
|
|
|
|
try {
|
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
|
values.add(keyVisitor(this, abs + 1))
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
|
}
|
|
} finally {
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1)
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
fun <T> readTableValues(stackIndex: Int = -1, valueVisitor: LuaThread.(stackIndex: Int) -> T): MutableList<T>? {
|
|
val abs = this.absStackIndex(stackIndex)
|
|
|
|
if (!this.isTable(abs))
|
|
return null
|
|
|
|
val values = ArrayList<T>()
|
|
|
|
this.push()
|
|
val top = this.stackTop
|
|
|
|
try {
|
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
|
values.add(valueVisitor(this, abs + 2))
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
|
}
|
|
} finally {
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1)
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
fun <K, V> readTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> K, valueVisitor: LuaThread.(stackIndex: Int) -> V): MutableList<Pair<K, V>>? {
|
|
val abs = this.absStackIndex(stackIndex)
|
|
|
|
if (!this.isTable(abs))
|
|
return null
|
|
|
|
val values = ArrayList<Pair<K, V>>()
|
|
|
|
this.push()
|
|
val top = this.stackTop
|
|
|
|
try {
|
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
|
values.add(keyVisitor(this, abs + 1) to valueVisitor(this, abs + 2))
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
|
}
|
|
} finally {
|
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1)
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
fun loadTableValue(stackIndex: Int = -2): LuaType {
|
|
return LuaType.valueOf(LuaJNR.INSTANCE.lua_gettable(this.pointer, stackIndex))
|
|
}
|
|
|
|
fun loadTableValue(name: String): LuaType {
|
|
push(name)
|
|
return loadTableValue()
|
|
}
|
|
|
|
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 popJson(): JsonElement? {
|
|
try {
|
|
return this.getJson()
|
|
} 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): LuaType {
|
|
return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name))
|
|
}
|
|
|
|
inner class ArgStack(val top: Int) {
|
|
val lua get() = this@LuaThread
|
|
var position = 1
|
|
|
|
fun peek(position: Int = this.position): LuaType {
|
|
if (position !in 1 .. top)
|
|
return LuaType.NONE
|
|
|
|
return this@LuaThread.typeAt(position)
|
|
}
|
|
|
|
fun hasNext(): Boolean {
|
|
return position <= top
|
|
}
|
|
|
|
fun nextString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String {
|
|
if (position !in 1 ..this.top)
|
|
throw IllegalArgumentException("bad argument #$position: string expected, got nil")
|
|
|
|
return this@LuaThread.getString(position, limit = limit)
|
|
?: throw IllegalArgumentException("bad argument #$position: string expected, got ${this@LuaThread.typeAt(position)}")
|
|
}
|
|
|
|
fun nextOptionalString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String? {
|
|
val type = this@LuaThread.typeAt(position)
|
|
|
|
if (type != LuaType.STRING && type != LuaType.NIL && type != LuaType.NONE)
|
|
throw IllegalArgumentException("bad argument #$position: string expected, got $type")
|
|
|
|
return this@LuaThread.getString(position, limit = limit)
|
|
}
|
|
|
|
fun nextLong(position: Int = this.position++): Long {
|
|
if (position !in 1 ..this.top)
|
|
throw IllegalArgumentException("bad argument #$position: number expected, got nil")
|
|
|
|
return this@LuaThread.getLong(position)
|
|
?: throw IllegalArgumentException("bad argument #$position: long expected, got ${this@LuaThread.typeAt(position)}")
|
|
}
|
|
|
|
fun nextJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement {
|
|
if (position !in 1 ..this.top)
|
|
throw IllegalArgumentException("bad argument #$position: json expected, got nil")
|
|
|
|
val value = this@LuaThread.getJson(position, limit = limit)
|
|
return value ?: throw IllegalArgumentException("bad argument #$position: anything expected, got ${this@LuaThread.typeAt(position)}")
|
|
}
|
|
|
|
fun nextOptionalJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
|
|
if (position !in 1 ..this.top)
|
|
return null
|
|
|
|
return this@LuaThread.getJson(position, limit = limit)
|
|
}
|
|
|
|
fun nextTable(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonObject {
|
|
if (position !in 1 ..this.top)
|
|
throw IllegalArgumentException("bad argument #$position: table expected, got nil")
|
|
|
|
val value = this@LuaThread.getTable(position, limit = limit)
|
|
return value ?: throw IllegalArgumentException("Lua code error: bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}")
|
|
}
|
|
|
|
fun nextAny(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
|
|
if (position !in 1 ..this.top)
|
|
throw IllegalArgumentException("bad argument #$position: json expected, got nil")
|
|
|
|
return this@LuaThread.getJson(position, limit = limit)
|
|
}
|
|
|
|
fun nextDouble(position: Int = this.position++): Double {
|
|
if (position !in 1 ..this.top)
|
|
throw IllegalArgumentException("bad argument #$position: number expected, got nil")
|
|
|
|
return this@LuaThread.getDouble(position)
|
|
?: throw IllegalArgumentException("bad argument #$position: number expected, got ${this@LuaThread.typeAt(position)}")
|
|
}
|
|
|
|
fun nextOptionalDouble(position: Int = this.position++): Double? {
|
|
val type = this@LuaThread.typeAt(position)
|
|
|
|
if (type != LuaType.NUMBER && type != LuaType.NIL && type != LuaType.NONE)
|
|
throw IllegalArgumentException("bad argument #$position: double expected, got $type")
|
|
|
|
return this@LuaThread.getDouble(position)
|
|
}
|
|
|
|
fun nextOptionalLong(position: Int = this.position++): Long? {
|
|
val type = this@LuaThread.typeAt(position)
|
|
|
|
if (type != LuaType.NUMBER && type != LuaType.NIL && type != LuaType.NONE)
|
|
throw IllegalArgumentException("bad argument #$position: integer expected, got $type")
|
|
|
|
return this@LuaThread.getLong(position)
|
|
}
|
|
|
|
fun nextOptionalBoolean(position: Int = this.position++): Boolean? {
|
|
val type = this@LuaThread.typeAt(position)
|
|
|
|
if (type == LuaType.NIL || type == LuaType.NONE)
|
|
return null
|
|
else if (type == LuaType.BOOLEAN)
|
|
return this@LuaThread.getBoolean(position)
|
|
else
|
|
throw IllegalArgumentException("Lua code error: bad argument #$position: boolean expected, got $type")
|
|
}
|
|
|
|
fun nextBoolean(position: Int = this.position++): Boolean {
|
|
if (position !in 1 ..this.top)
|
|
throw IllegalArgumentException("bad argument #$position: boolean expected, got nil")
|
|
|
|
return this@LuaThread.getBoolean(position)
|
|
?: throw IllegalArgumentException("bad argument #$position: boolean expected, got ${this@LuaThread.typeAt(position)}")
|
|
}
|
|
}
|
|
|
|
fun push(function: Fn, performanceCritical: Boolean) {
|
|
LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
|
|
val realLuaState: LuaThread
|
|
|
|
if (pointer.address() != it) {
|
|
realLuaState = LuaThread(LuaJNR.RUNTIME.memoryManager.newPointer(it), stringInterner = stringInterner)
|
|
realLuaState.initializeFrom(this)
|
|
} else {
|
|
realLuaState = this
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES)
|
|
|
|
fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) {
|
|
LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount)
|
|
}
|
|
|
|
fun push() {
|
|
LuaJNR.INSTANCE.lua_pushnil(this.pointer)
|
|
}
|
|
|
|
fun push(value: Long) {
|
|
LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value)
|
|
}
|
|
|
|
fun push(value: Long?) {
|
|
if (value == null) {
|
|
LuaJNR.INSTANCE.lua_pushnil(pointer)
|
|
} else {
|
|
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: Double?) {
|
|
if (value == null) {
|
|
LuaJNR.INSTANCE.lua_pushnil(pointer)
|
|
} else {
|
|
LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value)
|
|
}
|
|
}
|
|
|
|
fun push(value: Float?) {
|
|
if (value == null) {
|
|
LuaJNR.INSTANCE.lua_pushnil(pointer)
|
|
} else {
|
|
LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value.toDouble())
|
|
}
|
|
}
|
|
|
|
fun push(value: Boolean?) {
|
|
if (value == null) {
|
|
LuaJNR.INSTANCE.lua_pushnil(pointer)
|
|
} else {
|
|
LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0)
|
|
}
|
|
}
|
|
|
|
fun push(value: String) {
|
|
pushStringIntoThread(this, value)
|
|
}
|
|
|
|
fun copy(fromIndex: Int, toIndex: Int) {
|
|
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
|
|
}
|
|
|
|
fun dup() {
|
|
push()
|
|
copy(-2, -1)
|
|
}
|
|
|
|
fun dup(fromIndex: Int) {
|
|
push()
|
|
copy(fromIndex, -1)
|
|
}
|
|
|
|
fun setTop(topIndex: Int) {
|
|
LuaJNR.INSTANCE.lua_settop(pointer, topIndex)
|
|
}
|
|
|
|
fun storeRef(tableIndex: Int) {
|
|
LuaJNR.INSTANCE.luaL_ref(pointer, tableIndex)
|
|
}
|
|
|
|
fun pushTable(arraySize: Int = 0, hashSize: Int = 0) {
|
|
LuaJNR.INSTANCE.lua_createtable(pointer, arraySize, hashSize)
|
|
}
|
|
|
|
fun setTableValue(stackIndex: Int = -3) {
|
|
LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex)
|
|
}
|
|
|
|
fun setTableValue(key: String, value: Fn) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: String, value: JsonElement?) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
@Deprecated("Lua function is a stub")
|
|
fun setTableValueToStub(key: String) {
|
|
setTableValue(key) { throw NotImplementedError("NYI: $key") }
|
|
}
|
|
|
|
fun setTableValue(key: String, value: Int) {
|
|
this.push(key)
|
|
this.push(value.toLong())
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: String, value: Long) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: String, value: String?) {
|
|
value ?: return
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: String, value: Float) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: String, value: Double) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: Int, value: JsonElement?) {
|
|
return setTableValue(key.toLong(), value)
|
|
}
|
|
|
|
fun setTableValue(key: Int, value: Int) {
|
|
return setTableValue(key.toLong(), value)
|
|
}
|
|
|
|
fun setTableValue(key: Int, value: Long) {
|
|
return setTableValue(key.toLong(), value)
|
|
}
|
|
|
|
fun setTableValue(key: Int, value: String) {
|
|
return setTableValue(key.toLong(), value)
|
|
}
|
|
|
|
fun setTableValue(key: Int, value: Float) {
|
|
return setTableValue(key.toLong(), value)
|
|
}
|
|
|
|
fun setTableValue(key: Int, value: Double) {
|
|
return setTableValue(key.toLong(), value)
|
|
}
|
|
|
|
fun setTableValue(key: Long, value: JsonElement?) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: Long, value: Int) {
|
|
return setTableValue(key, value.toLong())
|
|
}
|
|
|
|
fun setTableValue(key: Long, value: Long) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: Long, value: String) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
fun setTableValue(key: Long, value: Float) {
|
|
return setTableValue(key, value.toDouble())
|
|
}
|
|
|
|
fun setTableValue(key: Long, value: Double) {
|
|
this.push(key)
|
|
this.push(value)
|
|
this.setTableValue()
|
|
}
|
|
|
|
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 -> {
|
|
this.loadGlobal("jarray")
|
|
this.call(numResults = 1)
|
|
val index = this.stackTop
|
|
|
|
for ((i, v) in value.withIndex()) {
|
|
this.push(i + 1L)
|
|
this.push(v)
|
|
|
|
this.setTableValue(index)
|
|
}
|
|
}
|
|
|
|
is JsonObject -> {
|
|
this.loadGlobal("jobject")
|
|
this.call(numResults = 1)
|
|
|
|
val index = this.stackTop
|
|
|
|
for ((k, v) in value.entrySet()) {
|
|
this.push(k)
|
|
this.push(v)
|
|
|
|
this.setTableValue(index)
|
|
}
|
|
}
|
|
|
|
else -> {
|
|
throw IllegalArgumentException(value.toString())
|
|
}
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
val LOGGER = LogManager.getLogger()
|
|
|
|
fun loadInternalScript(name: String): String {
|
|
return LuaThread::class.java.getResourceAsStream("/scripts/$name.lua")?.readAllBytes()?.toString(Charsets.UTF_8) ?: throw RuntimeException("/scripts/$name.lua is missing!")
|
|
}
|
|
|
|
private val globalScript by lazy { loadInternalScript("global") }
|
|
private val sharedBuffers = ThreadLocal<Long>()
|
|
|
|
private fun loadStringIntoBuffer(value: String): Long {
|
|
val bytes = value.toByteArray(Charsets.UTF_8)
|
|
|
|
if (bytes.size < DEFAULT_STRING_LIMIT) {
|
|
MemoryIO.getInstance().putByteArray(sharedStringBufferPtr, bytes, 0, bytes.size)
|
|
return sharedStringBufferPtr
|
|
} else {
|
|
throw RuntimeException("Too long string: $bytes bytes")
|
|
}
|
|
}
|
|
|
|
private fun makeNativeString(value: String): Long {
|
|
var bytes = value.toByteArray(Charsets.UTF_8)
|
|
bytes = bytes.copyOf(bytes.size + 1)
|
|
val p = MemoryIO.getInstance().allocateMemory(bytes.size.toLong(), false)
|
|
MemoryIO.getInstance().putByteArray(p, bytes, 0, bytes.size)
|
|
return p
|
|
}
|
|
|
|
private val __nils = makeNativeString("__nils")
|
|
private val __typehint = makeNativeString("__typehint")
|
|
|
|
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
|
|
|
|
Starbound.CLEANER.register(Thread.currentThread()) {
|
|
MemoryIO.getInstance().freeMemory(p2)
|
|
}
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
fun pushStringIntoThread(lua: LuaThread, value: String) {
|
|
if (value.isEmpty()) {
|
|
LuaJNR.INSTANCE.lua_pushlstring(lua.pointer, lua.pointer.address(), 0)
|
|
return
|
|
}
|
|
|
|
val bytes = value.toByteArray(Charsets.UTF_8)
|
|
|
|
if (bytes.size < DEFAULT_STRING_LIMIT) {
|
|
MemoryIO.getInstance().putByteArray(sharedStringBufferPtr, bytes, 0, bytes.size)
|
|
LuaJNR.INSTANCE.lua_pushlstring(lua.pointer, sharedStringBufferPtr, bytes.size.toLong())
|
|
} else {
|
|
val mem = MemoryIO.getInstance()
|
|
val alloc = mem.allocateMemory(bytes.size.toLong(), false)
|
|
|
|
if (alloc == 0L)
|
|
throw OutOfMemoryError("Unable to allocate ${bytes.size} bytes on heap")
|
|
|
|
try {
|
|
mem.putByteArray(alloc, bytes, 0, bytes.size)
|
|
LuaJNR.INSTANCE.lua_pushlstring(lua.pointer, alloc, bytes.size.toLong())
|
|
} finally {
|
|
mem.freeMemory(alloc)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 24
|
|
|
|
const val RECORD_STACK_TRACES = false
|
|
|
|
const val LUA_HINT_NONE = 0
|
|
const val LUA_HINT_ARRAY = 1
|
|
const val LUA_HINT_OBJECT = 2
|
|
}
|
|
}
|