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

717 lines
17 KiB
Kotlin

package ru.dbotthepony.kstarbound.lua
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 it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
import org.classdump.luna.ByteString
import org.classdump.luna.Conversions
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import org.classdump.luna.TableFactory
import org.classdump.luna.impl.NonsuspendableFunctionException
import org.classdump.luna.runtime.AbstractFunction3
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.io.writeSignedVarLong
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.util.IStruct2f
import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.util.IStruct3d
import ru.dbotthepony.kommons.util.IStruct3f
import ru.dbotthepony.kommons.util.IStruct3i
import ru.dbotthepony.kommons.util.IStruct4d
import ru.dbotthepony.kommons.util.IStruct4f
import ru.dbotthepony.kommons.util.IStruct4i
import ru.dbotthepony.kstarbound.json.BinaryJsonReader
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.world.physics.Poly
fun ExecutionContext.toVector2i(table: Any): Vector2i {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
return Vector2i(x.toInt(), y.toInt())
}
fun ExecutionContext.toVector2d(table: Any): Vector2d {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
return Vector2d(x.toDouble(), y.toDouble())
}
fun ExecutionContext.toLine2d(table: Any): Line2d {
val p0 = toVector2d(indexNoYield(table, 1L) ?: throw LuaRuntimeException("Invalid line: $table"))
val p1 = toVector2d(indexNoYield(table, 2L) ?: throw LuaRuntimeException("Invalid line: $table"))
return Line2d(p0, p1)
}
fun String?.toByteString(): ByteString? {
return if (this == null) null else ByteString.of(this)
}
fun ExecutionContext.toPoly(table: Table): Poly {
val vertices = ArrayList<Vector2d>()
for ((_, v) in table) {
vertices.add(toVector2d(v))
}
return Poly(vertices)
}
fun ExecutionContext.toVector2f(table: Any): Vector2f {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
return Vector2f(x.toFloat(), y.toFloat())
}
fun ExecutionContext.toColor(table: Any): RGBAColor {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L) ?: 255
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a Color, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a Color, but value at [2] is not a number: $y")
if (z !is Number) throw ClassCastException("Expected table representing a Color, but value at [3] is not a number: $z")
if (w !is Number) throw ClassCastException("Expected table representing a Color, but value at [4] is not a number: $w")
return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt())
}
fun ExecutionContext.toAABB(table: Any): AABB {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a AABB, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a AABB, but value at [2] is not a number: $y")
if (z !is Number) throw ClassCastException("Expected table representing a AABB, but value at [3] is not a number: $z")
if (w !is Number) throw ClassCastException("Expected table representing a AABB, but value at [4] is not a number: $w")
return AABB(Vector2d(x.toDouble(), y.toDouble()), Vector2d(z.toDouble(), w.toDouble()))
}
fun ExecutionContext.toAABBi(table: Any): AABBi {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [2] is not a number: $y")
if (z !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [3] is not a number: $z")
if (w !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [4] is not a number: $w")
return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt()))
}
fun toJsonFromLua(value: Any?): JsonElement {
return when (value) {
null, is JsonNull -> JsonNull.INSTANCE
is String -> JsonPrimitive(value.sbIntern())
is ByteString -> JsonPrimitive(value.decode().sbIntern())
is Number -> JsonPrimitive(value)
is Boolean -> InternedJsonElementAdapter.of(value)
is Table -> value.toJson()
else -> throw IllegalArgumentException("Unable to translate $value into json!")
}
}
fun Table.toJson(forceObject: Boolean = false): JsonElement {
val arrayValues = Long2ObjectAVLTreeMap<JsonElement>()
val hashValues = HashMap<String, JsonElement>()
val meta = metatable
var hint = LUA_HINT_NONE
if (meta != null) {
val getHint = meta["__typehint"]
if (getHint is Number) {
hint = getHint.toLong()
}
val nils = meta["__nils"]
if (nils is Table) {
// Nil entries just have a garbage integer as their value
for ((k, v) in nils) {
val ik = k.toLuaInteger()
if (ik != null) {
arrayValues[ik] = JsonNull.INSTANCE
} else {
hashValues[k.toString()] = JsonNull.INSTANCE
}
}
}
}
for ((k, v) in this) {
val ik = k.toLuaInteger()
if (ik != null) {
arrayValues[ik] = toJsonFromLua(v)
} else {
hashValues[k.toString()] = toJsonFromLua(v)
}
}
val interpretAsList = !forceObject && hashValues.isEmpty() && hint != LUA_HINT_OBJECT
if (interpretAsList) {
val list = JsonArray()
for ((k, v) in arrayValues) {
val ik = k.toInt() - 1
while (list.size() <= ik) {
list.add(JsonNull.INSTANCE)
}
list[ik] = v
}
return list
} else {
for ((k, v) in arrayValues) {
hashValues[(k - 1L).toString()] = v
}
return JsonObject().apply {
for ((k, v) in hashValues) {
this[k] = v
}
}
}
}
fun TableFactory.from(value: JsonElement?): Any? {
when (value) {
is JsonPrimitive -> {
if (value.isNumber) {
return when (val v = value.asNumber) {
is Float, is Double -> value.asDouble
is Int, is Long -> value.asLong
else -> {
// hi com.google.gson.internal.LazilyParsedNumber
val doubleBits by lazy { v.toDouble() }
if (v.toString().contains('.') || doubleBits % 1.0 != 0.0) {
v.toDouble()
} else {
v.toLong()
}
}
}
} else if (value.isString) {
return ByteString.of(value.asString)
} else if (value.isBoolean) {
return value.asBoolean
} else {
throw RuntimeException("unreachable code")
}
}
is JsonArray -> return from(value)
is JsonObject -> return from(value)
null, is JsonNull -> return null
else -> throw RuntimeException(value::class.qualifiedName)
}
}
private data class JsonTable(val metatable: Table, val nils: Table, val data: Table)
fun Any?.toLuaInteger(): Long? {
if (this == null)
return null
else if (this is Long)
return this
else if (this is Double) {
if (this % 1.0 == 0.0) {
return this.toLong()
} else {
return null
}
} else if (this is ByteString) {
val decoded = decode()
if (decoded.contains('.'))
return null
return decoded.toLongOrNull()
} else {
return null
}
}
const val LUA_HINT_NONE = 0L
const val LUA_HINT_ARRAY = 1L
const val LUA_HINT_OBJECT = 2L
private object JsonTableIndex : AbstractFunction3<Table, Any, Any?>() {
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
throw NonsuspendableFunctionException(this::class.java)
}
private val __nils: ByteString = ByteString.of("__nils")
override fun invoke(context: ExecutionContext, self: Table, key: Any, value: Any?) {
val meta = self.metatable
val nils = meta[__nils] as Table
// 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 (value == null) {
nils[key] = 0L
} else {
nils[key] = null as Any?
}
self[key] = value
}
}
private fun TableFactory.createJsonTable(typeHint: Long, size: Int, hash: Int): JsonTable {
val metatable = newTable()
val nils = newTable()
val data = newTable(size, hash)
metatable["__newindex"] = JsonTableIndex
metatable["__nils"] = nils
metatable["__typehint"] = typeHint
data.metatable = metatable
return JsonTable(metatable, nils, data)
}
fun TableFactory.from(value: JsonObject): Table {
val (_, nils, data) = createJsonTable(LUA_HINT_OBJECT, 0, value.size())
for ((k, v) in value.entrySet()) {
if (v.isJsonNull) {
nils[k] = 0L
} else {
data[k] = from(v)
}
}
return data
}
fun TableFactory.from(value: Int?): Long? {
return value?.toLong()
}
fun TableFactory.from(value: Long?): Long? {
return value
}
fun TableFactory.from(value: String?): ByteString? {
return value.toByteString()
}
fun TableFactory.from(value: ByteString?): ByteString? {
return value
}
fun TableFactory.from(value: JsonArray): Table {
val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size())
for ((i, v) in value.withIndex()) {
if (v.isJsonNull) {
nils[i + 1L] = 0L
} else {
data[i + 1L] = from(v)
}
}
return data
}
fun TableFactory.createJsonObject(): Table {
return createJsonTable(LUA_HINT_OBJECT, 0, 0).data
}
fun TableFactory.createJsonArray(): Table {
return createJsonTable(LUA_HINT_ARRAY, 0, 0).data
}
fun TableFactory.from(value: IStruct2d?): Table? {
value ?: return null
return newTable(2, 0).apply {
this[1L] = value.component1()
this[2L] = value.component2()
}
}
fun TableFactory.from(value: Poly?): Table? {
value ?: return null
return newTable(value.vertices.size, 0).apply {
value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) }
}
}
fun TableFactory.from(value: IStruct2i?): Table? {
value ?: return null
return newTable(2, 0).also {
it.rawset(1L, value.component1())
it.rawset(2L, value.component2())
}
}
fun TableFactory.from(value: IStruct3i?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.component1())
it.rawset(2L, value.component2())
it.rawset(3L, value.component3())
}
}
fun TableFactory.from(value: IStruct4i?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.component1())
it.rawset(2L, value.component2())
it.rawset(3L, value.component3())
it.rawset(4L, value.component4())
}
}
fun TableFactory.from(value: RGBAColor?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.redInt.toLong())
it.rawset(2L, value.greenInt.toLong())
it.rawset(3L, value.blueInt.toLong())
it.rawset(4L, value.alphaInt.toLong())
}
}
fun TableFactory.from(value: Collection<Any>): Table {
return newTable(value.size, 0).also {
for ((i, v) in value.withIndex()) {
it.rawset(i + 1L, v)
}
}
}
fun TableFactory.from(value: AABB?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.mins.x)
it.rawset(2L, value.mins.y)
it.rawset(3L, value.maxs.x)
it.rawset(4L, value.maxs.y)
}
}
fun TableFactory.tableFrom(collection: Collection<Any?>): Table {
val alloc = newTable(collection.size, 0)
for ((i, v) in collection.withIndex())
alloc[i + 1L] = v
return alloc
}
fun LuaState.getLine2d(stackIndex: Int = -1): Line2d? {
val abs = this.absStackIndex(stackIndex)
if (!this.isTable(abs))
return null
push(1)
loadTableValue(abs)
val x = getVector2d(abs + 1)
pop()
x ?: return null
push(2)
loadTableValue(abs)
val y = getVector2d(abs + 1)
pop()
y ?: return null
return Line2d(x, y)
}
fun LuaState.ArgStack.getLine2d(position: Int = this.position++): Line2d {
if (position !in 1 ..this.top)
throw IllegalArgumentException("Bad argument #$position: Line2d expected, got nil")
return lua.getLine2d(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Line2d expected, got ${lua.typeAt(position)}")
}
fun LuaState.getVector2d(stackIndex: Int = -1): Vector2d? {
val abs = this.absStackIndex(stackIndex)
if (!this.isTable(abs))
return null
push(1)
loadTableValue(abs)
val x = getDouble(abs + 1)
pop()
x ?: return null
push(2)
loadTableValue(abs)
val y = getDouble(abs + 1)
pop()
y ?: return null
return Vector2d(x, y)
}
fun LuaState.ArgStack.getVector2d(position: Int = this.position++): Vector2d {
if (position !in 1 ..this.top)
throw IllegalArgumentException("Bad argument #$position: Vector2d expected, got nil")
return lua.getVector2d(position)
?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Vector2d expected, got ${lua.typeAt(position)}")
}
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)
}
fun LuaState.push(value: IStruct4f) {
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: IStruct3f) {
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: IStruct2f) {
pushTable(arraySize = 2)
val table = stackTop
val (x, y) = value
push(1)
push(x)
setTableValue(table)
push(2)
push(y)
setTableValue(table)
}
fun LuaState.push(value: IStruct4d) {
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: IStruct3d) {
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: IStruct2d) {
pushTable(arraySize = 2)
val table = stackTop
val (x, y) = value
push(1)
push(x)
setTableValue(table)
push(2)
push(y)
setTableValue(table)
}