KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/UtilityBindings.kt

346 lines
9.4 KiB
Kotlin

package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonWriter
import org.classdump.luna.ByteString
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.util.XXHash32
import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.json.JsonPath
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandom
import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.util.random.toBytes
import ru.dbotthepony.kstarbound.util.toStarboundString
import java.io.StringWriter
import java.util.*
// TODO: Lua-side implementation for better performance?
private fun replaceTags(args: LuaThread.ArgStack): Int {
val string = args.nextString()
val tagsList = args.lua
.readTable(
args.position++,
{ getString(it) ?: throw IllegalStateException("Tags table contain non-string keys") },
{ getString(it) ?: throw IllegalStateException("Tags table contain non-string values") }
) ?: throw IllegalArgumentException("bad argument #2: table expected, got ${args.peek(args.position - 1)}")
val tags = tagsList.toMap()
args.lua.push(SBPattern.of(string).resolveOrSkip({ tags[it] }))
return 1
}
private fun hash32(args: LuaThread.ArgStack): Int {
val digest = XXHash32(2938728349.toInt())
while (args.hasNext()) {
when (args.peek()) {
LuaType.BOOLEAN -> digest.update(if (args.nextBoolean()) 1 else 0)
LuaType.NUMBER -> toBytes(digest::update, args.nextDouble())
LuaType.STRING -> digest.update(args.nextString().toByteArray(Charsets.UTF_8))
else -> throw IllegalArgumentException("bad argument #${args.position} to staticRandomI32")
}
}
return digest.digestAsInt()
}
private fun hash64(args: LuaThread.ArgStack): Long {
val digest = XXHash64(1997293021376312589L)
while (args.hasNext()) {
when (args.peek()) {
LuaType.BOOLEAN -> digest.update(if (args.nextBoolean()) 1 else 0)
LuaType.NUMBER -> toBytes(digest::update, args.nextDouble())
LuaType.STRING -> digest.update(args.nextString().toByteArray(Charsets.UTF_8))
else -> throw IllegalArgumentException("bad argument #${args.position} to staticRandomI32")
}
}
return digest.digestAsLong()
}
private fun staticRandomI32(args: LuaThread.ArgStack): Int {
args.lua.push(hash32(args).toLong())
return 1
}
private fun staticRandomI64(args: LuaThread.ArgStack): Int {
args.lua.push(hash64(args))
return 1
}
private fun staticRandomDouble(args: LuaThread.ArgStack): Int {
args.lua.push(hash64(args).ushr(11) * 1.1102230246251565E-16)
return 1
}
private fun staticRandomDoubleRange(args: LuaThread.ArgStack): Int {
val min = args.nextDouble()
val max = args.nextDouble()
val double = hash64(args).ushr(11) * 1.1102230246251565E-16
args.lua.push(double * (max - min) + min)
return 1
}
// FIXME: incorrect.
private fun staticRandomI64Range(args: LuaThread.ArgStack): Int {
val min = args.nextDouble()
val max = args.nextDouble()
val double = hash64(args).ushr(11) * 1.1102230246251565E-16
args.lua.push((min + (max - min + 1L) * double).toLong())
return 1
}
// FIXME: incorrect.
private fun staticRandomI32Range(args: LuaThread.ArgStack): Int {
val min = args.nextDouble()
val max = args.nextDouble()
val double = hash64(args).ushr(11) * 1.1102230246251565E-16
args.lua.push((min + (max - min + 1L) * double).toInt().toLong())
return 1
}
// TODO: Lua-side implementation for better performance?
private fun makeUuid(args: LuaThread.ArgStack): Int {
args.lua.push(UUID(args.lua.random.nextLong(), args.lua.random.nextLong()).toStarboundString())
return 1
}
private fun nrand(args: LuaThread.ArgStack): Int {
val stdev = args.nextOptionalDouble() ?: 1.0
val mean = args.nextOptionalDouble() ?: 0.0
args.lua.push(args.lua.random.nextNormalDouble(stdev, mean))
return 1
}
// TODO: Lua-side implementation for better performance?
private fun jsonMerge(args: LuaThread.ArgStack): Int {
val a = args.nextOptionalJson() ?: JsonNull.INSTANCE
val b = args.nextOptionalJson() ?: JsonNull.INSTANCE
args.lua.push(ru.dbotthepony.kstarbound.json.mergeJson(a, b))
return 1
}
// TODO: Lua-side implementation for better performance?
private fun jsonQuery(args: LuaThread.ArgStack): Int {
val json = args.nextOptionalJson() ?: JsonNull.INSTANCE
val path = args.nextString()
val default = args.nextOptionalJson() ?: JsonNull.INSTANCE
args.lua.push(JsonPath.query(path).get(json, default))
return 1
}
private fun printJson(args: LuaThread.ArgStack): Int {
val json = args.nextJson()
val pretty = args.nextOptionalBoolean() ?: false
val strBuilder = StringWriter()
val writer = JsonWriter(strBuilder)
writer.isLenient = true
if (pretty) {
writer.setIndent(" ")
}
TypeAdapters.JSON_ELEMENT.write(writer, json)
args.lua.push(strBuilder.toString())
return 1
}
fun provideUtilityBindings(lua: LuaThread) {
with(lua) {
push {
LuaThread.LOGGER.info(it.nextString())
0
}
storeGlobal("__print")
push {
LuaThread.LOGGER.warn(it.nextString())
0
}
storeGlobal("__print_warn")
push {
LuaThread.LOGGER.error(it.nextString())
0
}
storeGlobal("__print_error")
push {
LuaThread.LOGGER.fatal(it.nextString())
0
}
storeGlobal("__print_fatal")
push {
val path = it.nextString()
try {
load(Starbound.readLuaScript(path).join(), "@$path")
1
} catch (err: Exception) {
LuaThread.LOGGER.error("Exception loading Lua script $path", err)
throw err
}
}
storeGlobal("__require")
push {
push(random.nextDouble())
1
}
storeGlobal("__random_double")
push {
push(random.nextLong(it.nextLong(), it.nextLong()))
1
}
storeGlobal("__random_long")
push {
random = random(it.nextLong())
0
}
storeGlobal("__random_seed")
push {
push(it.lua.getNamedHandle(it.nextString()))
1
}
storeGlobal("gethandle")
push {
val find = it.lua.findNamedHandle(it.nextString())
if (find == null) {
0
} else {
push(find)
1
}
}
storeGlobal("findhandle")
}
lua.pushTable()
lua.dup()
lua.storeGlobal("sb")
lua.setTableValue("makeUuid", ::makeUuid)
lua.setTableValue("nrand", ::nrand)
lua.setTableValue("jsonMerge", ::jsonMerge)
lua.setTableValue("jsonQuery", ::jsonQuery)
lua.setTableValue("replaceTags", ::replaceTags)
lua.setTableValue("staticRandomI32", ::staticRandomI32)
lua.setTableValue("staticRandomI32Range", ::staticRandomI32Range)
lua.setTableValue("staticRandomI64", ::staticRandomI64)
lua.setTableValue("staticRandomI64Range", ::staticRandomI64Range)
lua.setTableValue("staticRandomDouble", ::staticRandomDouble)
lua.setTableValue("staticRandomDoubleRange", ::staticRandomDoubleRange)
lua.pushTable()
val randomMeta = lua.createHandle("RandomGenerator")
lua.pushBinding("init", LuaRandom::init)
lua.pushBinding("addEntropy", LuaRandom::addEntropy)
lua.pushBinding("randu32", LuaRandom::randu32)
lua.pushBinding("randi32", LuaRandom::randi32)
lua.pushBinding("randu64", LuaRandom::randu64)
lua.pushBinding("randi64", LuaRandom::randi64)
lua.pushBinding("randf", LuaRandom::randf)
lua.pushBinding("randd", LuaRandom::randd)
lua.pushBinding("randInt", LuaRandom::randLong)
lua.pushBinding("randUInt", LuaRandom::randLong)
lua.pushBinding("randLong", LuaRandom::randLong)
lua.pushBinding("randULong", LuaRandom::randLong)
lua.pushBinding("randn", LuaRandom::randn)
lua.pop()
lua.push("makeRandomSource")
lua.push { args ->
val seed = args.nextOptionalLong() ?: args.lua.random.nextLong()
lua.pushTable()
lua.push("__index")
lua.push(randomMeta)
lua.setTableValue()
lua.pushObject(LuaRandom(random(seed)))
1
}
lua.setTableValue()
lua.pushTable()
val noiseMeta = lua.createHandle("PerlinSource")
lua.pushBinding("get", LuaPerlinNoise::get)
lua.pushBinding("seed", LuaPerlinNoise::seed)
lua.pushBinding("parameters", LuaPerlinNoise::parameters)
lua.pushBinding("init", LuaPerlinNoise::init)
lua.pop()
lua.push("makePerlinSource")
lua.push { args ->
val params = AbstractPerlinNoise.of(Starbound.gson.fromJson(args.nextJson(), PerlinNoiseParameters::class.java))
lua.pushTable()
lua.push("__index")
lua.push(noiseMeta) // cyclic reference through GC root
lua.setTableValue()
lua.pushObject(LuaPerlinNoise(params))
1
}
lua.setTableValue()
lua.setTableValue("printJson", ::printJson)
lua.pop()
}
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) {
val config = lua.newTable()
lua.globals["config"] = config
config["getParameter"] = createConfigBinding(lookup)
}
fun createConfigBinding(lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) = luaFunction { name: ByteString, default: Any? ->
val get = lookup(this, JsonPath.query(name.decode()), default)
if (get is JsonElement)
returnBuffer.setTo(from(get))
else
returnBuffer.setTo(get)
}