Bring back most of native Lua code stuff

This commit is contained in:
DBotThePony 2024-12-14 20:12:09 +07:00
parent 941d09441f
commit c5dc4a465a
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 486 additions and 366 deletions

View File

@ -49,10 +49,20 @@ public interface LuaJNR {
public void luaopen_debug(@NotNull Pointer luaState); public void luaopen_debug(@NotNull Pointer luaState);
@Nullable @Nullable
public Pointer lua_tolstring(@NotNull Pointer luaState, int index, @LongLong long size); public Pointer lua_tolstring(@NotNull Pointer luaState, int index, @LongLong long statusCodeReturnPtr);
public int lua_load(@NotNull Pointer luaState, @LongLong long reader, long userData, @NotNull String chunkName, @NotNull String mode); public int lua_load(@NotNull Pointer luaState, @LongLong long reader, long userData, @NotNull String chunkName, @NotNull String mode);
/**
* Pops a table from the stack and sets it as the new metatable for the value at the given index.
*/
public void lua_setmetatable(@NotNull Pointer luaState, int stackIndex);
/**
* Pushes onto the stack the field e from the metatable of the object at index obj and returns the type of the pushed value. If the object does not have a metatable, or if the metatable does not have this field, pushes nothing and returns LUA_TNIL.
*/
public int luaL_getmetafield(@NotNull Pointer luaState, int stackIndex, @LongLong long stringPtr);
public interface lua_CFunction extends Closure { public interface lua_CFunction extends Closure {
int invoke(@NotNull Pointer luaState); int invoke(@NotNull Pointer luaState);

View File

@ -454,3 +454,88 @@ fun TableFactory.tableFrom(collection: Collection<Any?>): Table {
return alloc return alloc
} }
fun LuaState.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())
}
fun LuaState.ArgStack.getVector2i(position: Int = this.position++): Vector2i {
if (position !in 1 ..this.top)
throw IllegalArgumentException("Bad argument #$position: Vector2i expected, got nil")
return lua.getVector2i(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Vector2i expected, got ${lua.typeAt(position)}")
}
fun LuaState.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 LuaState.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 LuaState.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)
}

View File

@ -12,15 +12,11 @@ import com.kenai.jffi.Closure
import com.kenai.jffi.ClosureManager import com.kenai.jffi.ClosureManager
import com.kenai.jffi.MemoryIO import com.kenai.jffi.MemoryIO
import com.kenai.jffi.Type import com.kenai.jffi.Type
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import jnr.ffi.Pointer import jnr.ffi.Pointer
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kommons.util.IStruct2i import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.IStruct3i
import ru.dbotthepony.kommons.util.IStruct4i
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
@ -29,6 +25,7 @@ import java.lang.ref.Cleaner
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import kotlin.math.floor
import kotlin.system.exitProcess import kotlin.system.exitProcess
@Suppress("unused") @Suppress("unused")
@ -64,6 +61,16 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
this.storeGlobal("math") this.storeGlobal("math")
LuaJNR.INSTANCE.luaopen_utf8(this.pointer) LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
this.storeGlobal("utf8") this.storeGlobal("utf8")
push {
LOGGER.info(getString())
0
}
storeGlobal("__print")
load(globalScript, "@starbound.jar!/scripts/global.lua")
call()
} }
private val thread = Thread.currentThread() private val thread = Thread.currentThread()
@ -149,7 +156,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
private fun getStringRaw(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? { 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" } require(limit <= Int.MAX_VALUE) { "Can't allocate string bigger than ${Int.MAX_VALUE} characters" }
val stack = MemoryStack.stackPush() val stack = MemoryStack.stackPush()
val status = stack.mallocLong(1) val status = stack.mallocLong(1)
val p = LuaJNR.INSTANCE.lua_tolstring(this.pointer, this.absStackIndex(stackIndex), MemoryUtil.memAddress(status)) ?: return null val p = LuaJNR.INSTANCE.lua_tolstring(this.pointer, this.absStackIndex(stackIndex), MemoryUtil.memAddress(status)) ?: return null
@ -163,7 +170,8 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
val readBytes = ByteArray(len.toInt()) val readBytes = ByteArray(len.toInt())
p.get(0L, readBytes, 0, readBytes.size) p.get(0L, readBytes, 0, readBytes.size)
return this.stringInterner.intern(readBytes.toString(charset = Charsets.UTF_8)) //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 isCFunction(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_iscfunction(this.pointer, this.absStackIndex(stackIndex)) > 0
@ -260,44 +268,167 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
} }
fun getValue(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? { /**
* 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) val abs = this.absStackIndex(stackIndex)
if (abs == 0) if (abs == 0)
return null return null
return when (this.typeAt(abs)) { return when (val type = this.typeAt(abs)) {
LuaType.NONE -> null LuaType.NONE -> null
LuaType.NIL -> null // JsonNull.INSTANCE LuaType.NIL -> JsonNull.INSTANCE
LuaType.BOOLEAN -> InternedJsonElementAdapter.of(this.getBooleanRaw(abs)) 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.NUMBER -> JsonPrimitive(if (this.isInteger(abs)) this.getLongRaw(abs) else this.getDoubleRaw(abs))
LuaType.STRING -> JsonPrimitive(this.getStringRaw(abs, limit = limit)) 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.TABLE -> {
LuaType.USERDATA -> throw IllegalArgumentException("Can not get userdata from Lua stack at $abs") val values = HashMap<Any, JsonElement>()
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") 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? { fun getTable(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): JsonObject? {
val abs = this.absStackIndex(stackIndex) val abs = this.absStackIndex(stackIndex)
if (!this.isTable(abs)) if (!this.isTable(abs))
return null return null
return getTableRaw(abs, limit)
}
private fun getTableRaw(abs: Int, limit: Long = DEFAULT_STRING_LIMIT): JsonObject {
val pairs = JsonObject() val pairs = JsonObject()
this.push() this.push()
val top = this.stackTop val top = this.stackTop
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
val key = this.getValue(abs + 1, limit = limit) val key = this.getJson(abs + 1, limit = limit)
val value = this.getValue(abs + 2, limit = limit) val value = this.getJson(abs + 2, limit = limit)
if (key is JsonPrimitive && value != null) { if (key is JsonPrimitive && value != null) {
pairs.add(this.stringInterner.intern(key.asString), value) pairs.add(this.stringInterner.intern(key.asString), value)
@ -309,70 +440,9 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
return pairs 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? { fun getTableValue(stackIndex: Int = -2, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
this.loadTableValue(stackIndex) this.loadTableValue(stackIndex)
return this.getValue(limit = limit) return this.getJson(limit = limit)
} }
fun loadTableValue(stackIndex: Int = -2, allowNothing: Boolean = false) { fun loadTableValue(stackIndex: Int = -2, allowNothing: Boolean = false) {
@ -414,9 +484,9 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
} }
fun popValue(): JsonElement? { fun popJson(): JsonElement? {
try { try {
return this.getValue() return this.getJson()
} finally { } finally {
this.pop() this.pop()
} }
@ -460,14 +530,15 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
var position = 1 var position = 1
fun hasSomethingAt(position: Int): Boolean { fun hasSomethingAt(position: Int): Boolean {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" } if (position !in 1 ..this.top)
return false
return this@LuaState.typeAt(position) != LuaType.NONE return this@LuaState.typeAt(position) != LuaType.NONE
} }
fun hasSomethingAt(): Boolean { fun hasSomethingAt(): Boolean {
if (hasSomethingAt(this.position + 1)) { if (hasSomethingAt(this.position + 1))
return true return true
}
this.position++ this.position++
return false return false
@ -482,107 +553,87 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
fun getString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String { 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" } if (position !in 1 ..this.top)
return this@LuaState.getString(position, limit = limit) throw IllegalArgumentException("Bad argument #$position: string expected, got nil")
?: 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) return this@LuaState.getString(position, limit = limit)
?: throw IllegalArgumentException("Bad argument #$position: string expected, got ${this@LuaState.typeAt(position)}")
} }
fun getStringOrNull(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String? { fun getStringOrNull(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String? {
if (position > this.top) val type = this@LuaState.typeAt(position)
return null
return this.getStringOrNil(position, limit = limit) if (type != LuaType.STRING && type != LuaType.NIL && type != LuaType.NONE)
throw IllegalArgumentException("Bad argument #$position: string expected, got $type")
return this@LuaState.getString(position, limit = limit)
} }
fun getValue(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement { fun getLong(position: Int = this.position++): Long {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" } if (position !in 1 ..this.top)
val value = this@LuaState.getValue(position, limit = limit) throw IllegalArgumentException("Bad argument #$position: number expected, got nil")
return value ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: anything expected, got ${this@LuaState.typeAt(position)}")
return this@LuaState.getLong(position)
?: throw IllegalArgumentException("Bad argument #$position: long expected, got ${this@LuaState.typeAt(position)}")
}
fun getJson(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@LuaState.getJson(position, limit = limit)
return value ?: throw IllegalArgumentException("Bad argument #$position: anything expected, got ${this@LuaState.typeAt(position)}")
} }
fun getTable(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonObject { 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" } if (position !in 1 ..this.top)
throw IllegalArgumentException("Bad argument #$position: table expected, got nil")
val value = this@LuaState.getTable(position, limit = limit) 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)}") 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? { 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" } if (position !in 1 ..this.top)
return this@LuaState.getValue(position, limit = limit) throw IllegalArgumentException("Bad argument #$position: json expected, got nil")
}
fun getDoubleOrNil(position: Int = this.position++): Double? { return this@LuaState.getJson(position, limit = limit)
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 { fun getDouble(position: Int = this.position++): Double {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" } if (position !in 1 ..this.top)
throw IllegalArgumentException("Bad argument #$position: number expected, got nil")
return this@LuaState.getDouble(position) return this@LuaState.getDouble(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: double expected, got ${this@LuaState.typeAt(position)}") ?: throw IllegalArgumentException("Bad argument #$position: number expected, got ${this@LuaState.typeAt(position)}")
} }
fun getInt(position: Int = this.position++): Int { fun getDoubleOrNull(position: Int = this.position++): Double? {
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) val type = this@LuaState.typeAt(position)
if (type != LuaType.BOOLEAN && type != LuaType.NIL) if (type != LuaType.NUMBER && type != LuaType.NIL && type != LuaType.NONE)
throw IllegalArgumentException("Bad argument #$position: double expected, got $type")
return this@LuaState.getDouble(position)
}
fun getBooleanOrNull(position: Int = this.position++): Boolean? {
val type = this@LuaState.typeAt(position)
if (type == LuaType.NIL || type == LuaType.NONE)
return null
else if (type == LuaType.BOOLEAN)
return this@LuaState.getBoolean(position)
else
throw IllegalArgumentException("Lua code error: Bad argument #$position: boolean expected, got $type") throw IllegalArgumentException("Lua code error: Bad argument #$position: boolean expected, got $type")
return this@LuaState.getBoolean(position)
} }
fun getBool(position: Int = this.position++): Boolean { fun getBoolean(position: Int = this.position++): Boolean {
check(position in 1 ..this.top) { "JVM code error: Invalid argument position: $position" } if (position !in 1 ..this.top)
return this@LuaState.getBoolean(position) throw IllegalArgumentException("Bad argument #$position: boolean expected, got nil")
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: boolean expected, got ${this@LuaState.typeAt(position)}")
}
fun getBoolOrNull(position: Int = this.position++): Boolean? { return this@LuaState.getBoolean(position)
if (position > this.top) return null ?: throw IllegalArgumentException("Bad argument #$position: boolean expected, got ${this@LuaState.typeAt(position)}")
return this.getBoolOrNil(position)
} }
fun push() = this@LuaState.push() fun push() = this@LuaState.push()
@ -593,17 +644,8 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
fun push(value: Boolean) = this@LuaState.push(value) fun push(value: Boolean) = this@LuaState.push(value)
fun push(value: String?) = this@LuaState.push(value) fun push(value: String?) = this@LuaState.push(value)
fun push(value: JsonElement?) = this@LuaState.push(value) fun push(value: JsonElement?) = this@LuaState.push(value)
fun push(value: Registry.Entry<*>?) = this@LuaState.push(value)
fun pushFull(value: Registry.Entry<*>?) = this@LuaState.pushFull(value)
} }
/**
* Создаёт новое замыкание на стороне Lua. [function], будучи переданным в Lua,
* создаст новый **GC Root**.
*
* Вышестоящий код ОБЯЗАН использовать [ArgStack] и его [ArgStack.lua] для доступа к [LuaState], так как
* при вызове замыкания из Lua текущий [LuaState] может отличаться от того, которому был передан [function].
*/
fun push(function: ArgStack.() -> Int, performanceCritical: Boolean) { fun push(function: ArgStack.() -> Int, performanceCritical: Boolean) {
val weak = WeakReference(this) val weak = WeakReference(this)
val pointer = this.pointer val pointer = this.pointer
@ -674,63 +716,8 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
} }
/**
* Создаёт новое замыкание на стороне 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) 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() { fun push() {
LuaJNR.INSTANCE.lua_pushnil(this.pointer) LuaJNR.INSTANCE.lua_pushnil(this.pointer)
} }
@ -755,80 +742,6 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0) 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?) { fun push(value: String?) {
if (value == null) { if (value == null) {
this.push() this.push()
@ -837,7 +750,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
val bytes = value.toByteArray(Charsets.UTF_8) val bytes = value.toByteArray(Charsets.UTF_8)
if (bytes.size < 2 shl 16) { if (bytes.size < DEFAULT_STRING_LIMIT) {
MemoryIO.getInstance().putByteArray(sharedStringBufferPtr, bytes, 0, bytes.size) MemoryIO.getInstance().putByteArray(sharedStringBufferPtr, bytes, 0, bytes.size)
LuaJNR.INSTANCE.lua_pushlstring(this.pointer, sharedStringBufferPtr, bytes.size.toLong()) LuaJNR.INSTANCE.lua_pushlstring(this.pointer, sharedStringBufferPtr, bytes.size.toLong())
} else { } else {
@ -865,20 +778,6 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex) 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?) { fun setTableValue(key: String, value: JsonElement?) {
val table = this.stackTop val table = this.stackTop
this.push(key) this.push(key)
@ -923,45 +822,27 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
fun setTableValue(key: Int, value: JsonElement?) { fun setTableValue(key: Int, value: JsonElement?) {
val table = this.stackTop return setTableValue(key.toLong(), value)
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Int, value: Int) { fun setTableValue(key: Int, value: Int) {
val table = this.stackTop return setTableValue(key.toLong(), value)
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Int, value: Long) { fun setTableValue(key: Int, value: Long) {
val table = this.stackTop return setTableValue(key.toLong(), value)
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Int, value: String) { fun setTableValue(key: Int, value: String) {
val table = this.stackTop return setTableValue(key.toLong(), value)
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Int, value: Float) { fun setTableValue(key: Int, value: Float) {
val table = this.stackTop return setTableValue(key.toLong(), value)
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Int, value: Double) { fun setTableValue(key: Int, value: Double) {
val table = this.stackTop return setTableValue(key.toLong(), value)
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Long, value: JsonElement?) { fun setTableValue(key: Long, value: JsonElement?) {
@ -972,10 +853,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
fun setTableValue(key: Long, value: Int) { fun setTableValue(key: Long, value: Int) {
val table = this.stackTop return setTableValue(key, value.toLong())
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Long, value: Long) { fun setTableValue(key: Long, value: Long) {
@ -993,10 +871,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
fun setTableValue(key: Long, value: Float) { fun setTableValue(key: Long, value: Float) {
val table = this.stackTop return setTableValue(key, value.toDouble())
this.push(key)
this.push(value)
this.setTableValue(table)
} }
fun setTableValue(key: Long, value: Double) { fun setTableValue(key: Long, value: Double) {
@ -1030,7 +905,9 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
is JsonArray -> { is JsonArray -> {
val index = this.pushTable(arraySize = value.size()) this.loadGlobal("jarray")
this.call(numResults = 1)
val index = this.stackTop
for ((i, v) in value.withIndex()) { for ((i, v) in value.withIndex()) {
this.push(i + 1L) this.push(i + 1L)
@ -1041,7 +918,10 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
is JsonObject -> { is JsonObject -> {
val index = this.pushTable(hashSize = value.size()) this.loadGlobal("jobject")
this.call(numResults = 1)
val index = this.stackTop
for ((k, v) in value.entrySet()) { for ((k, v) in value.entrySet()) {
this.push(k) this.push(k)
@ -1057,28 +937,38 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
} }
} }
fun push(value: Registry.Entry<*>?) {
if (value == null)
push()
else
push(value.json)
}
fun pushFull(value: Registry.Entry<*>?) {
if (value == null)
push()
else {
pushTable(hashSize = 2)
setTableValue("path", value.file?.computeFullPath())
setTableValue("config", value.json)
}
}
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
fun loadInternalScript(name: String): String {
return LuaState::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 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() { private val sharedStringBufferPtr: Long get() {
var p: Long? = sharedBuffers.get() var p: Long? = sharedBuffers.get()
@ -1119,5 +1009,9 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
const val DEFAULT_STRING_LIMIT = 2L shl 16 const val DEFAULT_STRING_LIMIT = 2L shl 16
const val RECORD_STACK_TRACES = false const val RECORD_STACK_TRACES = false
const val LUA_HINT_NONE = 0
const val LUA_HINT_ARRAY = 1
const val LUA_HINT_OBJECT = 2
} }
} }

View File

@ -1,5 +1,17 @@
package ru.dbotthepony.kstarbound.lua package ru.dbotthepony.kstarbound.lua
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_NUMTYPES
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TBOOLEAN
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TFUNCTION
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TLIGHTUSERDATA
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TNIL
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TNONE
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TNUMBER
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TSTRING
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TTABLE
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TTHREAD
import ru.dbotthepony.kstarbound.lua.LuaState.Companion.LUA_TUSERDATA
enum class LuaType { enum class LuaType {
NONE, NONE,
NIL, NIL,
@ -12,4 +24,23 @@ enum class LuaType {
USERDATA, USERDATA,
THREAD, THREAD,
UMTYPES; UMTYPES;
companion object {
fun valueOf(value: Int): LuaType {
return when (value) {
LUA_TNONE -> NONE
LUA_TNIL -> NIL
LUA_TBOOLEAN -> BOOLEAN
LUA_TLIGHTUSERDATA -> LIGHTUSERDATA
LUA_TNUMBER -> NUMBER
LUA_TSTRING -> STRING
LUA_TTABLE -> TABLE
LUA_TFUNCTION -> FUNCTION
LUA_TUSERDATA -> USERDATA
LUA_TTHREAD -> THREAD
LUA_NUMTYPES -> UMTYPES
else -> throw RuntimeException("Invalid Lua type: $value")
}
}
}
} }

View File

@ -0,0 +1,17 @@
config = {}
local config = config
function config.getParameter(name, default)
if type(name) ~= 'string' then
error('config.getParameter: name must be a string, got ' .. type(name), 2)
end
local get = config._get(name)
if get == nil then
return default
else
return get
end
end

View File

@ -0,0 +1,56 @@
-- why not use _ENV anyway lol
self = self or {}
local LUA_HINT_NONE = 0
local LUA_HINT_ARRAY = 1
local LUA_HINT_OBJECT = 2
-- this replicates original engine code, but it shouldn't work in first place
local function __newindex(self, key, value)
local nils = getmetatable(self).__nils
-- If we are setting an entry to nil, need to add a bogus integer entry
-- to the __nils table, otherwise need to set the entry *in* the __nils
-- table to nil and remove it.
-- TODO: __newindex is called only when assigning non-existing keys to values,
-- TODO: as per Lua manual.
-- TODO: Chucklefish weren't aware of this?
if key == nil then
nils[key] = true
else
nils[key] = nil
end
rawset(self, key, value)
end
function jobject()
return setmetatable({}, {
__newindex = __newindex,
__nils = {},
__typehint = LUA_HINT_OBJECT
})
end
function jarray()
return setmetatable({}, {
__newindex = __newindex,
__nils = {},
__typehint = LUA_HINT_ARRAY
})
end
local __print = __print
function print(...)
local values = {...}
for i, v in ipairs(values) do
values[i] = tostring(v)
end
__print(table.concat(values, '\t'))
end

View File

@ -0,0 +1,27 @@
message = {
handlers = {}
}
local message = message
function message.setHandler(name, handler)
if type(name) ~= 'string' then
error('message.setHandler: Handler name must be a string, got ' .. type(name), 2)
end
if type(handler) ~= 'function' then
error('message.setHandler: Handler itself must be a function, got ' .. type(handler), 2)
end
message.subscribe(name)
message.handlers[name] = handler
end
function message.call(name, ...)
local handler = message.handlers[name]
if handler ~= nil then
return handler(...)
end
end