Partial utility bindings

This commit is contained in:
DBotThePony 2024-12-18 13:02:13 +07:00
parent d627526088
commit 658dffc832
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 288 additions and 117 deletions

View File

@ -119,6 +119,9 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
* Example: Status controller scripts now get `monster` bindings when running in context of Monster's status controller, etc
* `behavior.behavior` third argument (specified commonly as `_ENV`) is ignored and can be omitted (set to nil)
* It was used solely to get Lua engine (Lua execution context), and could have been deprecated long time ago even in original engine, because there is now a way in original engine to get Lua engine when binding is called
* Added `sb.logFatal`, similar to other log functions
* `print(...)` now prints to both console (stdout) and logs
* `sb.log` functions now accept everything `string.format` accepts, and not only `%s` and `%%`
## Random
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point

View File

@ -374,6 +374,14 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
return createDescriptor().toJson()
}
fun toTable(allocator: TableFactory): Table? {
if (isEmpty) {
return null
}
return createDescriptor().toTable(allocator)
}
class Adapter(gson: Gson) : TypeAdapter<ItemStack>() {
override fun write(out: JsonWriter, value: ItemStack?) {
val json = value?.toJson()

View File

@ -206,8 +206,8 @@ class LuaEnvironment : StateContext {
// TODO: NYI, maybe polyfill?
Utf8Lib.installInto(this, globals)
provideRootBindings(this)
provideUtilityBindings(this)
// provideRootBindings(this)
// provideUtilityBindings(this)
}
private val scripts = ObjectArraySet<ChunkFactory>()

View File

@ -22,6 +22,8 @@ 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
@ -71,47 +73,8 @@ class LuaThread private constructor(
LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
this.storeGlobal("utf8")
push {
LOGGER.info(it.nextString())
0
}
storeGlobal("__print")
push {
val path = it.nextString()
try {
load(Starbound.readLuaScript(path).join(), "@$path")
1
} catch (err: Exception) {
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")
provideUtilityBindings(this)
provideRootBindings(this)
load(globalScript, "@starbound.jar!/scripts/global.lua")
call()
@ -818,6 +781,10 @@ class LuaThread private constructor(
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")
@ -851,6 +818,13 @@ class LuaThread private constructor(
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")

View File

@ -1,25 +1,27 @@
package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonElement
import org.apache.logging.log4j.LogManager
import com.google.gson.JsonNull
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.util.XXHash32
import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.kstarbound.math.vector.Vector2d
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.LuaThread.Companion
import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.luaFunctionArray
import ru.dbotthepony.kstarbound.lua.luaFunctionN
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
@ -28,26 +30,17 @@ 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.staticRandom32
import ru.dbotthepony.kstarbound.util.random.staticRandom32FromList
import ru.dbotthepony.kstarbound.util.random.staticRandom64FromList
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
import ru.dbotthepony.kstarbound.util.random.staticRandomDoubleFromList
import ru.dbotthepony.kstarbound.util.random.staticRandomIntFromList
import ru.dbotthepony.kstarbound.util.random.staticRandomLong
import ru.dbotthepony.kstarbound.util.random.staticRandomLongFromList
import ru.dbotthepony.kstarbound.util.random.toBytes
import ru.dbotthepony.kstarbound.util.toStarboundString
import java.util.*
import java.util.random.RandomGenerator
private val LOGGER = LogManager.getLogger()
private val logInfo = luaFunctionN("logInfo") { args ->
LOGGER.info(args.nextString().toString().format(*args.copyRemaining()))
}
private val logWarn = luaFunctionN("logWarn") { args ->
LOGGER.warn(args.nextString().toString().format(*args.copyRemaining()))
}
private val logError = luaFunctionN("logError") { args ->
LOGGER.error(args.nextString().toString().format(*args.copyRemaining()))
}
import kotlin.collections.ArrayList
private val interpolateSinEase = luaFunctionArray { args ->
if (args.size < 3)
@ -71,75 +64,224 @@ private val interpolateSinEase = luaFunctionArray { args ->
}
}
private val replaceTags = luaFunction { string: ByteString, tags: Table ->
returnBuffer.setTo(SBPattern.of(string.toString()).resolveOrSkip({ tags[it]?.toString() }).toByteString())
// 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 val makePerlinSource = luaFunction { settings: Table ->
returnBuffer.setTo(LuaPerlinNoise(AbstractPerlinNoise.of(Starbound.gson.fromJson(settings.toJson(), PerlinNoiseParameters::class.java))))
}
private val staticRandomI32 = luaFunctionArray {
returnBuffer.setTo(staticRandom32(*it))
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")
}
}
private val staticRandomDouble = luaFunctionArray {
returnBuffer.setTo(staticRandomDouble(*it))
return digest.digestAsInt()
}
private val staticRandomDoubleRange = luaFunctionN("staticRandomDoubleRange") {
val min = it.nextFloat()
val max = it.nextFloat()
returnBuffer.setTo(staticRandomDouble(*it.copyRemaining()) * (max - min) + min)
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")
}
}
private val staticRandomI32Range = luaFunctionN("staticRandomI32Range") {
val min = it.nextInteger()
val max = it.nextInteger()
returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining()))
return digest.digestAsLong()
}
private val mergeJson = luaFunction { a: Any?, b: Any? ->
returnBuffer.setTo(from(ru.dbotthepony.kstarbound.json.mergeJson(toJsonFromLua(a), toJsonFromLua(b))))
private fun staticRandomI32(args: LuaThread.ArgStack): Int {
args.lua.push(hash32(args).toLong())
return 1
}
fun provideUtilityBindings(lua: LuaEnvironment) {
val table = lua.newTable()
lua.globals["sb"] = table
table["makeUuid"] = luaFunction {
returnBuffer.setTo(UUID(lua.random.nextLong(), lua.random.nextLong()).toStarboundString().toByteString())
private fun staticRandomI64(args: LuaThread.ArgStack): Int {
args.lua.push(hash64(args))
return 1
}
table["logInfo"] = logInfo
table["logWarn"] = logWarn
table["logError"] = logError
table["nrand"] = luaFunctionN("nrand") { args ->
val stdev = args.nextOptionalFloat() ?: 1.0
val mean = args.nextOptionalFloat() ?: 0.0
lua.random.nextNormalDouble(stdev, mean)
private fun staticRandomDouble(args: LuaThread.ArgStack): Int {
args.lua.push(hash64(args).ushr(11) * 1.1102230246251565E-16)
return 1
}
table["print"] = lua.globals["tostring"]
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
}
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")
}
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)
/*table["print"] = lua.globals["tostring"]
table["printJson"] = lua.globals["tostring"]
table["interpolateSinEase"] = interpolateSinEase
table["replaceTags"] = replaceTags
table["makeRandomSource"] = luaFunction { seed: Long? ->
returnBuffer.setTo(LuaRandomGenerator(random(seed ?: lua.random.nextLong())))
}
table["makePerlinSource"] = makePerlinSource
table["makePerlinSource"] = makePerlinSource*/
table["staticRandomI32"] = staticRandomI32
table["staticRandomI64"] = staticRandomI32
table["staticRandomDouble"] = staticRandomDouble
lua.pop()
table["staticRandomDoubleRange"] = staticRandomDoubleRange
table["staticRandomI32Range"] = staticRandomI32Range
table["staticRandomI64Range"] = staticRandomI32Range
table["jsonMerge"] = mergeJson
}
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) {

View File

@ -35,19 +35,19 @@ fun random(seed: Long = System.nanoTime()): RandomGenerator {
*/
val threadLocalRandom: RandomGenerator by ThreadLocal.withInitial { random() }
private fun toBytes(accept: ByteConsumer, value: Short) {
fun toBytes(accept: ByteConsumer, value: Short) {
accept.accept(value.toByte())
accept.accept((value.toInt() ushr 8).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Int) {
fun toBytes(accept: ByteConsumer, value: Int) {
accept.accept(value.toByte())
accept.accept((value ushr 8).toByte())
accept.accept((value ushr 16).toByte())
accept.accept((value ushr 24).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Long) {
fun toBytes(accept: ByteConsumer, value: Long) {
accept.accept(value.toByte())
accept.accept((value ushr 8).toByte())
accept.accept((value ushr 16).toByte())
@ -58,15 +58,19 @@ private fun toBytes(accept: ByteConsumer, value: Long) {
accept.accept((value ushr 56).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Double) {
fun toBytes(accept: ByteConsumer, value: Double) {
toBytes(accept, value.toBits())
}
private fun toBytes(accept: ByteConsumer, value: Float) {
fun toBytes(accept: ByteConsumer, value: Float) {
toBytes(accept, value.toBits())
}
fun staticRandom32(vararg values: Any?): Int {
return staticRandom32FromList(values.asIterable())
}
fun staticRandom32FromList(values: Iterable<Any?>): Int {
val digest = XXHash32(2938728349.toInt())
for (value in values) {
@ -96,6 +100,10 @@ fun staticRandomDouble(vararg values: Any?): Double {
return staticRandom64(*values).ushr(11) * 1.1102230246251565E-16
}
fun staticRandomDoubleFromList(values: Iterable<Any?>): Double {
return staticRandom64FromList(values).ushr(11) * 1.1102230246251565E-16
}
fun staticRandomInt(min: Int, max: Int, vararg values: Any?): Int {
val hash = staticRandomDouble(*values)
return (min + (max - min + 1) * hash).toInt()
@ -106,7 +114,21 @@ fun staticRandomLong(min: Long, max: Long, vararg values: Any?): Long {
return (min + (max - min + 1L) * hash).toLong()
}
fun staticRandomIntFromList(min: Int, max: Int, values: Iterable<Any?>): Int {
val hash = staticRandomDoubleFromList(values)
return (min + (max - min + 1) * hash).toInt()
}
fun staticRandomLongFromList(min: Long, max: Long, values: Iterable<Any?>): Long {
val hash = staticRandomDoubleFromList(values)
return (min + (max - min + 1L) * hash).toLong()
}
fun staticRandom64(vararg values: Any?): Long {
return staticRandom64FromList(values.asIterable())
}
fun staticRandom64FromList(values: Iterable<Any?>): Long {
val digest = XXHash64(1997293021376312589L)
for (value in values) {

View File

@ -147,6 +147,10 @@ end
do
local __print = __print
local __print_warn = __print_warn
local __print_error = __print_error
local __print_fatal = __print_fatal
local format = string.format
function print(...)
local values = {}
@ -158,6 +162,22 @@ do
__print(table.concat(values, '\t'))
end
function sb.logInfo(text, ...)
__print(format(text, ...))
end
function sb.logWarn(text, ...)
__print_warn(format(text, ...))
end
function sb.logError(text, ...)
__print_error(format(text, ...))
end
function sb.logFatal(text, ...)
__print_fatal(format(text, ...))
end
end
do
@ -258,3 +278,5 @@ do
__random_seed(floor(seed))
end
end