346 lines
9.4 KiB
Kotlin
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)
|
|
}
|