Compare commits

...

5 Commits

35 changed files with 1176 additions and 475 deletions

View File

@ -13,12 +13,7 @@ static int lua_jniFunc(lua_State *state) {
jint result = (*env)->CallIntMethod(env, *lua_JCClosure, callback, (long long) state); jint result = (*env)->CallIntMethod(env, *lua_JCClosure, callback, (long long) state);
if (result <= -1) { if (result <= -1) {
const char* errMsg = lua_tostring(state, -1); return lua_error(state);
if (errMsg == NULL)
return luaL_error(state, "Internal JVM Error");
return luaL_error(state, "%s", errMsg);
} }
return result; return result;

View File

@ -32,9 +32,9 @@ public interface LuaJNR {
@IgnoreError @IgnoreError
public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback); public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback);
@IgnoreError @IgnoreError
public int lua_callk(@NotNull Pointer luaState, int numArgs, int numResults, @LongLong long ctx, @LongLong long callback);
@IgnoreError
public long lua_atpanic(@NotNull Pointer luaState, @LongLong long fn); public long lua_atpanic(@NotNull Pointer luaState, @LongLong long fn);
@IgnoreError
public long luaL_traceback(@NotNull Pointer luaState, @NotNull Pointer forState, @NotNull String message, int level);
/** /**
* Creates a new thread, pushes it on the stack, and returns a pointer to a lua_State that represents this new thread. The new thread returned by this function shares with the original thread its global environment, but has an independent execution stack. * Creates a new thread, pushes it on the stack, and returns a pointer to a lua_State that represents this new thread. The new thread returned by this function shares with the original thread its global environment, but has an independent execution stack.

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.kstarbound.defs.actor.behavior package ru.dbotthepony.kstarbound.defs.actor.behavior
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
/** /**
@ -16,4 +17,5 @@ data class BehaviorNodeDefinition(
*/ */
val properties: ImmutableMap<String, NodeParameter> = ImmutableMap.of(), val properties: ImmutableMap<String, NodeParameter> = ImmutableMap.of(),
val output: ImmutableMap<String, NodeOutput> = ImmutableMap.of(), val output: ImmutableMap<String, NodeOutput> = ImmutableMap.of(),
val script: AssetPath? = null,
) )

View File

@ -15,7 +15,7 @@ import ru.dbotthepony.kstarbound.lua.userdata.NodeParameterType
data class NodeOutput(val type: NodeParameterType, val key: String? = null, val ephemeral: Boolean = false) { data class NodeOutput(val type: NodeParameterType, val key: String? = null, val ephemeral: Boolean = false) {
fun push(lua: LuaThread) { fun push(lua: LuaThread) {
lua.pushTable(hashSize = 3) lua.pushTable(hashSize = 3)
lua.setTableValue("type", type.ordinal) lua.setTableValue("type", type.ordinal + 1)
if (key != null) if (key != null)
lua.setTableValue("key", key) lua.setTableValue("key", key)

View File

@ -20,7 +20,7 @@ data class NodeParameter(val type: NodeParameterType, val value: NodeParameterVa
fun push(lua: LuaThread) { fun push(lua: LuaThread) {
lua.pushTable(hashSize = 3) lua.pushTable(hashSize = 3)
lua.setTableValue("type", type.ordinal) lua.setTableValue("type", type.ordinal + 1)
if (value.key != null) { if (value.key != null) {
lua.setTableValue("key", value.key) lua.setTableValue("key", value.key)

View File

@ -274,7 +274,6 @@ data class ItemDescriptor(
push(parameters) push(parameters)
push(level) push(level)
push(seed) push(seed)
5
}, { getJson() as JsonObject to getJson() as JsonObject }).get() }, { getJson() as JsonObject to getJson() as JsonObject }).get()
} finally { } finally {
lua.close() lua.close()

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.json package ru.dbotthepony.kstarbound.json
import com.github.luben.zstd.ZstdDictDecompress
import com.github.luben.zstd.ZstdInputStreamNoFinalizer import com.github.luben.zstd.ZstdInputStreamNoFinalizer
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
@ -36,7 +37,7 @@ private enum class InflateType {
NONE NONE
} }
private fun <T> ByteArray.callRead(inflate: InflateType, callable: DataInputStream.() -> T): T { private fun <T> ByteArray.callRead(inflate: InflateType, dictionary: ZstdDictDecompress? = null, callable: DataInputStream.() -> T): T {
val stream = FastByteArrayInputStream(this) val stream = FastByteArrayInputStream(this)
when (inflate) { when (inflate) {
@ -53,12 +54,17 @@ private fun <T> ByteArray.callRead(inflate: InflateType, callable: DataInputStre
} }
InflateType.ZSTD -> { InflateType.ZSTD -> {
val data = DataInputStream(BufferedInputStream(ZstdInputStreamNoFinalizer(stream), 0x10000)) val f = ZstdInputStreamNoFinalizer(stream)
if (dictionary != null)
f.setDict(dictionary)
val data = DataInputStream(BufferedInputStream(f, 0x10000))
try { try {
return callable(data) return callable(data)
} finally { } finally {
data.close() f.close()
} }
} }
@ -76,9 +82,9 @@ fun ByteArray.readJsonElementInflated(): JsonElement = callRead(InflateType.ZLIB
fun ByteArray.readJsonObjectInflated(): JsonObject = callRead(InflateType.ZLIB) { readJsonObject() } fun ByteArray.readJsonObjectInflated(): JsonObject = callRead(InflateType.ZLIB) { readJsonObject() }
fun ByteArray.readJsonArrayInflated(): JsonArray = callRead(InflateType.ZLIB) { readJsonArray() } fun ByteArray.readJsonArrayInflated(): JsonArray = callRead(InflateType.ZLIB) { readJsonArray() }
fun ByteArray.readJsonElementZstd(): JsonElement = callRead(InflateType.ZSTD) { readJsonElement() } fun ByteArray.readJsonElementZstd(dictionary: ZstdDictDecompress? = null): JsonElement = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonElement() }
fun ByteArray.readJsonObjectZstd(): JsonObject = callRead(InflateType.ZSTD) { readJsonObject() } fun ByteArray.readJsonObjectZstd(dictionary: ZstdDictDecompress? = null): JsonObject = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonObject() }
fun ByteArray.readJsonArrayZstd(): JsonArray = callRead(InflateType.ZSTD) { readJsonArray() } fun ByteArray.readJsonArrayZstd(dictionary: ZstdDictDecompress? = null): JsonArray = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonArray() }
/** /**
* Позволяет читать двоичный JSON прямиком в [JsonElement] * Позволяет читать двоичный JSON прямиком в [JsonElement]

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.json package ru.dbotthepony.kstarbound.json
import com.github.luben.zstd.ZstdDictCompress
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
@ -21,7 +22,7 @@ private enum class DeflateType {
NONE NONE
} }
private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6, callable: DataOutputStream.(T) -> Unit): ByteArray { private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6, zstdDictionary: ZstdDictCompress? = null, callable: DataOutputStream.(T) -> Unit): ByteArray {
val stream = FastByteArrayOutputStream() val stream = FastByteArrayOutputStream()
when (deflate) { when (deflate) {
@ -38,6 +39,9 @@ private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6,
val s = ZstdOutputStreamNoFinalizer(stream) val s = ZstdOutputStreamNoFinalizer(stream)
s.setLevel(zstdCompressionLevel) s.setLevel(zstdCompressionLevel)
if (zstdDictionary != null)
s.setDict(zstdDictionary)
DataOutputStream(BufferedOutputStream(s, 0x10000)).use { DataOutputStream(BufferedOutputStream(s, 0x10000)).use {
callable(it, this) callable(it, this)
} }
@ -57,9 +61,9 @@ fun JsonElement.writeJsonElementDeflated(): ByteArray = callWrite(DeflateType.ZL
fun JsonObject.writeJsonObjectDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonObject(it) } fun JsonObject.writeJsonObjectDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonObject(it) }
fun JsonArray.writeJsonArrayDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonArray(it) } fun JsonArray.writeJsonArrayDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonArray(it) }
fun JsonElement.writeJsonElementZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonElement(it) } fun JsonElement.writeJsonElementZstd(level: Int = 6, dictionary: ZstdDictCompress? = null): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level, zstdDictionary = dictionary) { writeJsonElement(it) }
fun JsonObject.writeJsonObjectZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonObject(it) } fun JsonObject.writeJsonObjectZstd(level: Int = 6, dictionary: ZstdDictCompress? = null): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level, zstdDictionary = dictionary) { writeJsonObject(it) }
fun JsonArray.writeJsonArrayZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonArray(it) } fun JsonArray.writeJsonArrayZstd(level: Int = 6, dictionary: ZstdDictCompress? = null): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level, zstdDictionary = dictionary) { writeJsonArray(it) }
fun DataOutputStream.writeJsonElement(value: JsonElement) { fun DataOutputStream.writeJsonElement(value: JsonElement) {
when (value) { when (value) {

View File

@ -1,6 +1,10 @@
package ru.dbotthepony.kstarbound.lua package ru.dbotthepony.kstarbound.lua
data class CommonHandleRegistry( data class CommonHandleRegistry(
val future: LuaHandle, val future: LuaHandle = LuaHandle.Nil,
val pathFinder: LuaHandle, val pathFinder: LuaHandle = LuaHandle.Nil,
) ) {
companion object {
val EMPTY = CommonHandleRegistry()
}
}

View File

@ -18,6 +18,7 @@ import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.util.floorToInt
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
// TODO: error reporting when argument was provided, but it is malformed // TODO: error reporting when argument was provided, but it is malformed
@ -176,10 +177,10 @@ fun LuaThread.getVector2iOrAABB(stackIndex: Int = -1): Either<Vector2i, AABB>? {
fun LuaThread.ArgStack.nextVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB> { fun LuaThread.ArgStack.nextVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB> {
if (position !in 1 ..this.top) if (position !in 1 ..this.top)
throw IllegalArgumentException("bad argument #$position: Vector2d expected, got nil") throw IllegalArgumentException("bad argument #$position: Vector2i expected, got nil")
return lua.getVector2iOrAABB(position) return lua.getVector2iOrAABB(position)
?: throw IllegalArgumentException("bad argument #$position: Vector2d or AABB expected, got ${lua.typeAt(position)}") ?: throw IllegalArgumentException("bad argument #$position: Vector2i or AABB expected, got ${lua.typeAt(position)}")
} }
fun LuaThread.ArgStack.nextOptionalVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB>? { fun LuaThread.ArgStack.nextOptionalVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB>? {
@ -223,18 +224,20 @@ fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getLong() // FIXME: original engine parity, where it casts doubles into ints
// while it seems okay, it can cause undesired side effects
val x = getDouble()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getLong() val y = getDouble()
pop() pop()
y ?: return null y ?: return null
return Vector2i(x.toInt(), y.toInt()) return Vector2i(x.floorToInt(), y.floorToInt())
} }
fun LuaThread.ArgStack.nextVector2i(position: Int = this.position++): Vector2i { fun LuaThread.ArgStack.nextVector2i(position: Int = this.position++): Vector2i {
@ -262,31 +265,31 @@ fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getLong() val x = getFloat()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getLong() val y = getFloat()
pop() pop()
y ?: return null y ?: return null
push(3) push(3)
loadTableValue(abs) loadTableValue(abs)
val z = getLong() val z = getFloat()
pop() pop()
z ?: return null z ?: return null
push(4) push(4)
loadTableValue(abs) loadTableValue(abs)
val w = getLong() ?: 255L val w = getFloat() ?: 255f
pop() pop()
return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) return RGBAColor(x / 255f, y / 255f, z / 255f, w / 255f)
} }
fun LuaThread.ArgStack.nextColor(position: Int = this.position++): RGBAColor { fun LuaThread.ArgStack.nextColor(position: Int = this.position++): RGBAColor {
@ -365,32 +368,33 @@ fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getLong() // FIXME: original engine parity
val x = getDouble()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getLong() val y = getDouble()
pop() pop()
y ?: return null y ?: return null
push(3) push(3)
loadTableValue(abs) loadTableValue(abs)
val z = getLong() val z = getDouble()
pop() pop()
z ?: return null z ?: return null
push(4) push(4)
loadTableValue(abs) loadTableValue(abs)
val w = getLong() val w = getDouble()
pop() pop()
w ?: return null w ?: return null
return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt())) return AABBi(Vector2i(x.floorToInt(), y.floorToInt()), Vector2i(z.floorToInt(), w.floorToInt()))
} }
fun LuaThread.ArgStack.nextAABBi(position: Int = this.position++): AABBi { fun LuaThread.ArgStack.nextAABBi(position: Int = this.position++): AABBi {

View File

@ -16,17 +16,6 @@ const val LUA_ERRMEM = 4
const val LUA_ERRERR = 5 const val LUA_ERRERR = 5
class InvalidLuaSyntaxException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) class InvalidLuaSyntaxException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
class LuaMemoryAllocException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) class LuaMemoryAllocException(message: String? = null, cause: Throwable? = null) : Error(message, cause)
class LuaGCException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
class LuaException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) class LuaException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
class LuaRuntimeException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) class LuaRuntimeException(message: String? = null, cause: Throwable? = null, writeStackTrace: Boolean = true) : RuntimeException(message, cause, true, writeStackTrace)
fun throwPcallError(code: Int) {
when (code) {
LUA_OK -> {}
LUA_ERRRUN -> throw LuaException("Runtime Error")
LUA_ERRMEM -> throw LuaMemoryAllocException()
LUA_ERRERR -> throw LuaException("Exception inside Exception handler")
else -> throw LuaException("Unknown Lua Loading error: $code")
}
}

View File

@ -1,27 +1,144 @@
package ru.dbotthepony.kstarbound.lua package ru.dbotthepony.kstarbound.lua
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonPrimitive
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
import java.io.Closeable import java.io.Closeable
import java.lang.ref.Cleaner.Cleanable import java.lang.ref.Cleaner.Cleanable
import java.lang.ref.WeakReference
class LuaHandle(private val parent: LuaSharedState, val handle: Int, val key: Any?) : Closeable { interface LuaHandle : Closeable {
fun push(into: LuaThread)
override fun close() {}
fun toJson(): JsonElement
val type: LuaType
object Nil : LuaHandle {
override fun push(into: LuaThread) {
into.push()
}
override fun toString(): String {
return "LuaHandle.Nil"
}
override fun toJson(): JsonElement {
return JsonNull.INSTANCE
}
override val type: LuaType
get() = LuaType.NIL
}
object True : LuaHandle {
override fun push(into: LuaThread) {
into.push(true)
}
override fun toString(): String {
return "LuaHandle.True"
}
override fun toJson(): JsonElement {
return InternedJsonElementAdapter.TRUE
}
override val type: LuaType
get() = LuaType.BOOLEAN
}
object False : LuaHandle {
override fun push(into: LuaThread) {
into.push(false)
}
override fun toString(): String {
return "LuaHandle.False"
}
override fun toJson(): JsonElement {
return InternedJsonElementAdapter.FALSE
}
override val type: LuaType
get() = LuaType.BOOLEAN
}
class LLong(val value: Long) : LuaHandle {
override fun push(into: LuaThread) {
into.push(value)
}
override fun equals(other: Any?): Boolean {
return other is LLong && other.value == value
}
override fun hashCode(): Int {
return value.hashCode()
}
override fun toString(): String {
return "LuaHandle.LLong[$value]"
}
override fun toJson(): JsonElement {
return JsonPrimitive(value)
}
override val type: LuaType
get() = LuaType.NUMBER
}
class LDouble(val value: Double) : LuaHandle {
override fun push(into: LuaThread) {
into.push(value)
}
override fun equals(other: Any?): Boolean {
return other is LDouble && other.value == value
}
override fun hashCode(): Int {
return value.hashCode()
}
override fun toString(): String {
return "LuaHandle.LDouble[$value]"
}
override fun toJson(): JsonElement {
return JsonPrimitive(value)
}
override val type: LuaType
get() = LuaType.NUMBER
}
class Regular(private val parent: LuaSharedState, val handle: Int, val key: Any?) : LuaHandle {
private val cleanable: Cleanable private val cleanable: Cleanable
var isValid = true var isValid = true
private set private set
override val type: LuaType by lazy(LazyThreadSafetyMode.NONE) {
check(isValid) { "Tried to use NULL handle!" }
parent.handlesThread.typeAt(handle)
}
init { init {
val parent = parent val parent = WeakReference(parent)
val handle = handle val handle = handle
val key = key val key = key
cleanable = Starbound.CLEANER.register(this) { cleanable = Starbound.CLEANER.register(this) {
parent.freeHandle(handle, key) parent.get()?.freeHandle(handle, key)
} }
} }
fun push(into: LuaThread) { override fun push(into: LuaThread) {
check(isValid) { "Tried to use NULL handle!" } check(isValid) { "Tried to use NULL handle!" }
parent.handlesThread.push() parent.handlesThread.push()
parent.handlesThread.copy(handle, -1) parent.handlesThread.copy(handle, -1)
@ -33,4 +150,20 @@ class LuaHandle(private val parent: LuaSharedState, val handle: Int, val key: An
cleanable.clean() cleanable.clean()
isValid = false isValid = false
} }
override fun toJson(): JsonElement {
check(isValid) { "Tried to use NULL handle!" }
return parent.handlesThread.getJson(handle)!!
}
override fun toString(): String {
return "LuaHandle[$parent - $handle / $key]"
}
}
companion object {
fun boolean(of: Boolean): LuaHandle {
if (of) return True else return False
}
}
} }

View File

@ -38,19 +38,16 @@ class LuaMessageHandlerComponent(val lua: LuaThread, val nameProvider: () -> Str
return handlers[name] return handlers[name]
} }
inline fun handle(message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? { fun handle(message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? {
val handler = lookupHandler(message) ?: return null val handler = lookupHandler(message) ?: return null
val top = lua.stackTop val top = lua.stackTop
try { try {
lua.push(handler) return lua.call(1, {
lua.push(isLocal) push(handler)
val amountOfArguments = arguments(lua) push(isLocal)
check(amountOfArguments >= 0) { "Invalid amount of arguments to pass to Lua handler: $amountOfArguments" } arguments(lua)
}, { getJson(it) })
lua.call(amountOfArguments + 1, 1)
return lua.getJson()
} catch (err: Throwable) { } catch (err: Throwable) {
if (logPacer.consumeAndReturnDeadline() <= 0L) if (logPacer.consumeAndReturnDeadline() <= 0L)
LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err) LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err)

View File

@ -30,11 +30,24 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
check(isValid) { "Tried to use NULL LuaState!" } check(isValid) { "Tried to use NULL LuaState!" }
} }
var errorToStringFunction by Delegates.notNull<LuaHandle>()
private set
var errorTrapFunction by Delegates.notNull<LuaHandle>()
private set
override fun toString(): String {
return "LuaSharedState[$handlesThread]"
}
override fun close() { override fun close() {
if (!isValid) return if (!isValid) return
isValid = false isValid = false
namedHandles.clear() namedHandles.clear()
cleanable.clean() cleanable.clean()
errorToStringFunction = LuaHandle.Nil
errorTrapFunction = LuaHandle.Nil
commonHandles = CommonHandleRegistry.EMPTY
} }
fun initializeHandles(mainThread: LuaThread) { fun initializeHandles(mainThread: LuaThread) {
@ -45,6 +58,36 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
future = future, future = future,
pathFinder = pathFinder, pathFinder = pathFinder,
) )
handlesThread.push {
//it.lua.push(it.nextObject<Throwable?>(-1)?.stackTraceToString() ?: it.nextObject<Any?>(-1).toString())
it.lua.push(it.nextObject<Any?>().toString())
1
}
errorToStringFunction = allocateHandle(null)
handlesThread.push {
val peek = it.peek()
if (peek == LuaType.STRING) {
it.lua.traceback(it.lua.getString()!!, 1)
val err = LuaRuntimeException(it.lua.getString())
it.lua.push(err)
} else if (peek == LuaType.USERDATA) {
val obj = it.nextObject<Any>()
if (obj is Throwable && obj !is LuaRuntimeException) {
it.lua.traceback(obj.toString(), 1)
val err = LuaRuntimeException(it.lua.getString(), cause = obj, writeStackTrace = false)
it.lua.push(err)
}
}
1
}
errorTrapFunction = allocateHandle(null)
} }
fun freeHandle(handle: Int, key: Any?) { fun freeHandle(handle: Int, key: Any?) {
@ -79,10 +122,10 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
if (freeHandles.isEmpty()) { if (freeHandles.isEmpty()) {
if (nextHandle % 10 == 0) { if (nextHandle % 10 == 0) {
handlesThread.ensureExtraCapacity(10) handlesThread.ensureExtraCapacity(20)
} }
return LuaHandle(this, ++nextHandle, name).also { return LuaHandle.Regular(this, ++nextHandle, name).also {
if (name != null) namedHandles[name] = it if (name != null) namedHandles[name] = it
} }
} else { } else {
@ -92,7 +135,7 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
handlesThread.copy(-1, handle) handlesThread.copy(-1, handle)
handlesThread.pop() handlesThread.pop()
return LuaHandle(this, handle, name).also { return LuaHandle.Regular(this, handle, name).also {
if (name != null) namedHandles[name] = it if (name != null) namedHandles[name] = it
} }
} }

View File

@ -90,14 +90,19 @@ class LuaThread private constructor(
this.storeGlobal("math") this.storeGlobal("math")
LuaJNR.INSTANCE.luaopen_utf8(this.pointer) LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
this.storeGlobal("utf8") this.storeGlobal("utf8")
LuaJNR.INSTANCE.luaopen_debug(this.pointer)
this.storeGlobal("debug")
LuaJNR.INSTANCE.luaopen_os(this.pointer)
this.storeGlobal("os")
sharedState.initializeHandles(this) sharedState.initializeHandles(this)
provideUtilityBindings(this) provideUtilityBindings(this)
provideRootBindings(this) provideRootBindings(this)
call {
load(globalScript, "@/internal/global.lua") load(globalScript, "@/internal/global.lua")
call() }
} }
fun interface Fn { fun interface Fn {
@ -204,115 +209,219 @@ class LuaThread private constructor(
closure.dispose() closure.dispose()
} }
fun call(numArgs: Int = 0, numResults: Int = 0): Int { private fun pcall(numArgs: Int, numResults: Int, handler: Int) {
sharedState.cleanup() val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, handler, 0L, 0L)
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
if (status == LUA_ERRRUN) { if (status == LUA_ERRRUN) {
val errObject = typeAt(-1)
if (errObject == LuaType.STRING)
throw LuaRuntimeException(this.getString()) throw LuaRuntimeException(this.getString())
else if (errObject == LuaType.USERDATA)
throw getObject() as Throwable
else
throw RuntimeException("Lua raised an error, but it has invalid value as error: $errObject")
} else if (status == LUA_ERRMEM) {
throw LuaMemoryAllocException()
} else if (status == LUA_ERRERR) {
throw LuaException("Exception inside Exception handler")
}
} }
return status fun callConditional(block: LuaThread.() -> Boolean) {
} sharedState.cleanup()
/**
* Returns boolean indicating whenever function exists
*/
inline fun invokeGlobal(name: String, arguments: LuaThread.() -> Int): Boolean {
val top = stackTop val top = stackTop
push(sharedState.errorTrapFunction)
try { try {
val type = loadGlobal(name) if (block(this)) {
if (type != LuaType.FUNCTION) val newTop = stackTop
return false
val numArguments = arguments(this) if (newTop == top + 1) {
check(numArguments >= 0) { "Invalid amount of arguments provided to Lua function" } throw IllegalArgumentException("No function was pushed to stack")
call(numArguments) }
return true
pcall(newTop - top - 2, 0, top + 1)
}
} finally { } finally {
setTop(top) LuaJNR.INSTANCE.lua_settop(pointer, top)
} }
} }
/** fun call(block: LuaThread.() -> Unit) {
* Returns boolean indicating whenever function exists return callConditional { block(this); true }
*/ }
fun invokeGlobal(name: String, numArguments: Int): Boolean {
fun <T> callConditional(numResults: Int, block: LuaThread.() -> Boolean, resultHandler: LuaThread.(firstStackIndex: Int) -> T): KOptional<T> {
require(numResults >= 0) { "Invalid amount of results to get from Lua call: $numResults" }
sharedState.cleanup()
val top = stackTop val top = stackTop
push(sharedState.errorTrapFunction)
try { try {
val type = loadGlobal(name) if (block(this)) {
val newTop = stackTop
if (type != LuaType.FUNCTION) { if (newTop == top + 1) {
pop(numArguments) throw IllegalArgumentException("No function was pushed to stack")
return false
} }
call(numArguments) pcall(newTop - top - 2, numResults, top + 1)
return true return KOptional(resultHandler(this, top + 2))
} finally { } else {
setTop(top)
}
}
/**
* Returns empty [KOptional] if function does not exist
*/
inline fun <T> invokeGlobal(name: String, numResults: Int, arguments: LuaThread.() -> Int, results: LuaThread.(firstValue: Int) -> T): KOptional<T> {
require(numResults > 0) { "Invalid amount of results: $numResults" }
val top = stackTop
try {
val type = loadGlobal(name)
if (type != LuaType.FUNCTION)
return KOptional() return KOptional()
}
val numArguments = arguments(this)
call(numArguments, numResults)
return KOptional(results(this, top + 1))
} finally { } finally {
setTop(top) LuaJNR.INSTANCE.lua_settop(pointer, top)
} }
} }
inline fun <T> eval(chunk: String, name: String = "eval", numResults: Int, arguments: LuaThread.() -> Int, results: LuaThread.(firstValue: Int) -> T): T { fun <T> call(numResults: Int, block: LuaThread.() -> Unit, resultHandler: LuaThread.(firstStackIndex: Int) -> T): T {
require(numResults > 0) { "Invalid amount of results: $numResults" } return callConditional(numResults, { block(this); true }, resultHandler).value
}
fun callConditional(numResults: Int, block: LuaThread.() -> Boolean): List<LuaHandle>? {
require(numResults >= 0) { "Invalid amount of results to get from Lua call: $numResults" }
sharedState.cleanup()
val top = stackTop val top = stackTop
push(sharedState.errorTrapFunction)
try { try {
val numArguments = arguments(this) if (!block(this))
load(chunk, name) return null
call(numArguments, numResults)
return results(this, top + 1) val newTop = stackTop
} finally {
setTop(top) if (newTop == top + 1) {
throw IllegalArgumentException("No function was pushed to stack")
}
pcall(newTop - top - 2, numResults, top + 1)
val handles = ArrayList<LuaHandle>()
for (i in 1 .. numResults) {
val stackIndex = top + 1 + i
when (typeAt(stackIndex)) {
LuaType.NONE, LuaType.NIL -> handles.add(LuaHandle.Nil)
LuaType.BOOLEAN -> handles.add(LuaHandle.boolean(getBooleanRaw(stackIndex)))
LuaType.NUMBER -> {
if (isInteger(stackIndex)) {
handles.add(LuaHandle.LLong(getLongRaw(stackIndex)))
} else {
handles.add(LuaHandle.LDouble(getDoubleRaw(stackIndex)))
} }
} }
inline fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Int) { else -> {
val top = stackTop if (i != numResults) {
push()
copy(stackIndex, -1)
}
try { moveStackValuesOnto(sharedState.handlesThread)
val numArguments = arguments(this) handles.add(sharedState.allocateHandle(null))
load(chunk, name) }
call(numArguments, 0) }
}
return handles
} finally { } finally {
setTop(top) LuaJNR.INSTANCE.lua_settop(pointer, top)
}
}
fun call(numResults: Int, block: LuaThread.() -> Unit): List<LuaHandle> {
return callConditional(numResults, { block(this); true })!!
}
fun traceback(message: String, level: Int = 0) {
LuaJNR.INSTANCE.luaL_traceback(pointer, pointer, message, level)
}
fun invokeGlobal(name: String): Boolean {
var exists = false
callConditional {
val type = loadGlobal(name)
if (type != LuaType.FUNCTION)
return@callConditional false
exists = true
return@callConditional true
}
return exists
}
fun invokeGlobal(name: String, arguments: LuaThread.() -> Unit): Boolean {
var exists = false
callConditional {
val type = loadGlobal(name)
if (type != LuaType.FUNCTION)
return@callConditional false
arguments(this)
exists = true
return@callConditional true
}
return exists
}
fun invokeGlobal(name: String, numResults: Int, arguments: LuaThread.() -> Unit): List<LuaHandle>? {
return callConditional(numResults) {
val type = loadGlobal(name)
if (type != LuaType.FUNCTION)
return@callConditional false
arguments(this)
return@callConditional true
}
}
fun <T> invokeGlobal(name: String, numResults: Int, arguments: LuaThread.() -> Unit, resultHandler: LuaThread.(firstStackIndex: Int) -> T): KOptional<T> {
return callConditional(numResults, {
val type = loadGlobal(name)
if (type != LuaType.FUNCTION)
return@callConditional false
arguments(this)
return@callConditional true
}, resultHandler)
}
fun <T> eval(chunk: String, name: String = "eval", numResults: Int, arguments: LuaThread.() -> Unit, results: LuaThread.(firstValue: Int) -> T): T {
return call(numResults, {
load(chunk, name)
arguments(this)
}, results)
}
fun eval(chunk: String, name: String = "eval", numResults: Int, arguments: LuaThread.() -> Unit): List<LuaHandle> {
return call(numResults) {
load(chunk, name)
arguments(this)
}
}
fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Unit) {
return call {
load(chunk, name)
arguments(this)
} }
} }
fun eval(chunk: String, name: String = "eval"): JsonElement { fun eval(chunk: String, name: String = "eval"): JsonElement {
val top = stackTop return call(1, {
try {
load(chunk, name) load(chunk, name)
call(numResults = 1) }, { getJson(it)!! })
return getJson() ?: JsonNull.INSTANCE
} finally {
setTop(top)
}
} }
private val attachedScripts = ArrayList<String>() private val attachedScripts = ArrayList<String>()
@ -333,8 +442,9 @@ class LuaThread private constructor(
try { try {
// minor hiccups during unpopulated script cache should be tolerable // minor hiccups during unpopulated script cache should be tolerable
for ((chunk, path) in loadScripts) { for ((chunk, path) in loadScripts) {
call {
load(chunk.join(), "@$path") load(chunk.join(), "@$path")
call() }
} }
} catch (err: Exception) { } catch (err: Exception) {
LOGGER.error("Failed to attach scripts to Lua environment", err) LOGGER.error("Failed to attach scripts to Lua environment", err)
@ -343,16 +453,18 @@ class LuaThread private constructor(
try { try {
if (callInit) { if (callInit) {
callConditional {
val type = loadGlobal("init") val type = loadGlobal("init")
if (type == LuaType.FUNCTION) { if (type == LuaType.NIL || type == LuaType.NONE) {
call() return@callConditional false
} else if (type == LuaType.NIL || type == LuaType.NONE) { } else if (type != LuaType.FUNCTION) {
pop()
} else {
pop()
throw LuaRuntimeException("init is not a function: $type") throw LuaRuntimeException("init is not a function: $type")
} }
true
}
} }
} catch (err: Exception) { } catch (err: Exception) {
LOGGER.error("Failed to call init() in Lua environment", err) LOGGER.error("Failed to call init() in Lua environment", err)
@ -371,8 +483,9 @@ class LuaThread private constructor(
sharedState.ensureValid() sharedState.ensureValid()
if (initCalled) { if (initCalled) {
// minor hiccups during unpopulated script cache should be tolerable // minor hiccups during unpopulated script cache should be tolerable
call {
load(Starbound.readLuaScript(script).join(), "@$script") load(Starbound.readLuaScript(script).join(), "@$script")
call() }
} else { } else {
attachedScripts.add(script) attachedScripts.add(script)
} }
@ -710,8 +823,8 @@ class LuaThread private constructor(
try { try {
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
keyVisitor(this, abs + 1) keyVisitor(this, top)
valueVisitor(this, abs + 2) valueVisitor(this, top + 1)
LuaJNR.INSTANCE.lua_settop(this.pointer, top) LuaJNR.INSTANCE.lua_settop(this.pointer, top)
} }
} finally { } finally {
@ -734,7 +847,7 @@ class LuaThread private constructor(
try { try {
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
values.add(keyVisitor(this, abs + 1)) values.add(keyVisitor(this, top))
LuaJNR.INSTANCE.lua_settop(this.pointer, top) LuaJNR.INSTANCE.lua_settop(this.pointer, top)
} }
} finally { } finally {
@ -757,7 +870,7 @@ class LuaThread private constructor(
try { try {
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
values.add(valueVisitor(this, abs + 2)) values.add(valueVisitor(this, top + 1))
LuaJNR.INSTANCE.lua_settop(this.pointer, top) LuaJNR.INSTANCE.lua_settop(this.pointer, top)
} }
} finally { } finally {
@ -780,7 +893,7 @@ class LuaThread private constructor(
try { try {
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
values.add(keyVisitor(this, abs + 1) to valueVisitor(this, abs + 2)) values.add(keyVisitor(this, top) to valueVisitor(this, top + 1))
LuaJNR.INSTANCE.lua_settop(this.pointer, top) LuaJNR.INSTANCE.lua_settop(this.pointer, top)
} }
} finally { } finally {
@ -1106,7 +1219,7 @@ class LuaThread private constructor(
} }
} }
private fun closure(p: Long, function: Fn, performanceCritical: Boolean): Int { private fun closure(p: Long, function: Fn): Int {
sharedState.cleanup() sharedState.cleanup()
val realLuaState: LuaThread val realLuaState: LuaThread
@ -1118,68 +1231,32 @@ class LuaThread private constructor(
} }
val args = realLuaState.ArgStack(realLuaState.stackTop) val args = realLuaState.ArgStack(realLuaState.stackTop)
val rememberStack: ArrayList<String>?
if (performanceCritical) {
rememberStack = null
} else {
rememberStack = ArrayList(Exception().stackTraceToString().split('\n'))
rememberStack.removeAt(0) // java.lang. ...
// rememberStack.removeAt(0) // at ... push( ... )
}
try { try {
val value = function.invoke(args) val value = function.invoke(args)
check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" } check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" }
return value return value
} catch (err: Throwable) { } catch (err: Throwable) {
try { realLuaState.push(err)
if (performanceCritical) {
realLuaState.push(err.stackTraceToString())
return -1
} else {
rememberStack!!
val newStack = err.stackTraceToString().split('\n').toMutableList()
val rememberIterator = rememberStack.listIterator(rememberStack.size)
val iterator = newStack.listIterator(newStack.size)
var hit = false
while (rememberIterator.hasPrevious() && iterator.hasPrevious()) {
val a = rememberIterator.previous()
val b = iterator.previous()
if (a == b) {
hit = true
iterator.remove()
} else {
break
}
}
if (hit) {
newStack[newStack.size - 1] = "\t<...>"
}
realLuaState.push(newStack.joinToString("\n"))
return -1
}
} catch(err2: Throwable) {
realLuaState.push("JVM suffered an exception while handling earlier exception: ${err2.stackTraceToString()}; earlier: ${err.stackTraceToString()}")
return -1 return -1
} }
} }
fun push(err: Throwable) {
pushTable()
push("__tostring")
push(sharedState.errorToStringFunction)
setTableValue()
pushObject(err)
} }
fun push(function: Fn, performanceCritical: Boolean) { fun push(function: Fn) {
sharedState.ensureValid() sharedState.ensureValid()
LuaJNI.lua_pushcclosure(pointer.address()) {
closure(it, function, performanceCritical)
}
}
fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES) LuaJNI.lua_pushcclosure(pointer.address()) {
closure(it, function)
}
}
fun <T> push(self: T, function: Binding<T>) { fun <T> push(self: T, function: Binding<T>) {
push { push {
@ -1363,6 +1440,17 @@ class LuaThread private constructor(
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex) LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
} }
fun swap(indexA: Int, indexB: Int) {
if (indexA == indexB) return
val absA = if (indexA < 0) indexA - 1 else indexA
val absB = if (indexB < 0) indexB - 1 else indexB
push()
copy(absA, -1)
copy(absB, absA)
copy(-1, absB)
pop()
}
fun dup() { fun dup() {
push() push()
copy(-2, -1) copy(-2, -1)
@ -1590,8 +1678,8 @@ class LuaThread private constructor(
} }
is JsonArray -> { is JsonArray -> {
this.loadGlobal("jarray") val handle = call(1) { loadGlobal("jarray") }.first()
this.call(numResults = 1) handle.push(this)
val index = this.stackTop val index = this.stackTop
for ((i, v) in value.withIndex()) { for ((i, v) in value.withIndex()) {
@ -1603,8 +1691,8 @@ class LuaThread private constructor(
} }
is JsonObject -> { is JsonObject -> {
this.loadGlobal("jobject") val handle = call(1) { loadGlobal("jobject") }.first()
this.call(numResults = 1) handle.push(this)
val index = this.stackTop val index = this.stackTop
@ -1622,6 +1710,10 @@ class LuaThread private constructor(
} }
} }
override fun toString(): String {
return "LuaThread at ${pointer.address()}"
}
companion object { companion object {
val LOGGER = LogManager.getLogger() val LOGGER = LogManager.getLogger()

View File

@ -38,24 +38,27 @@ class LuaUpdateComponent(val lua: LuaThread, val name: Any) {
if (steps >= stepCount) { if (steps >= stepCount) {
steps %= stepCount steps %= stepCount
val type = lua.loadGlobal("update") lua.callConditional {
val type = loadGlobal("update")
if (type != lastType) { /*if (type != lastType) {
lastType = type lastType = type
if (type != LuaType.FUNCTION) { if (type != LuaType.FUNCTION) {
LOGGER.warn("Lua environment for $name has $type as global 'update', script update wasn't called") LOGGER.warn("Lua environment for $name has $type as global 'update', script update wasn't called")
} }
} }*/
if (type == LuaType.FUNCTION) { if (type == LuaType.FUNCTION) {
preRun() preRun()
lua.push(stepCount * Starbound.TIMESTEP) push(stepCount * Starbound.TIMESTEP)
lua.call(1) return@callConditional true
} else { } else {
lua.pop() return@callConditional false
} }
} }
}
} }
fun update(delta: Double) { fun update(delta: Double) {

View File

@ -100,12 +100,12 @@ private fun setDropPool(self: MonsterEntity, args: LuaThread.ArgStack): Int {
private fun toAbsolutePosition(self: MonsterEntity, args: LuaThread.ArgStack): Int { private fun toAbsolutePosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.movement.getAbsolutePosition(args.nextVector2d())) args.lua.push(self.movement.getAbsolutePosition(args.nextVector2d()))
return 0 return 1
} }
private fun mouthPosition(self: MonsterEntity, args: LuaThread.ArgStack): Int { private fun mouthPosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.mouthPosition) args.lua.push(self.mouthPosition)
return 0 return 1
} }
// This callback is registered here rather than in // This callback is registered here rather than in

View File

@ -386,6 +386,7 @@ fun provideServerWorldBindings(self: ServerWorld, lua: LuaThread) {
lua.setTableValueToStub("setLayerEnvironmentBiome") lua.setTableValueToStub("setLayerEnvironmentBiome")
lua.setTableValueToStub("setPlanetType") lua.setTableValueToStub("setPlanetType")
lua.load(script, "@/internal/server_world.lua") lua.call {
lua.call() load(script, "@/internal/server_world.lua")
}
} }

View File

@ -160,40 +160,39 @@ private fun printJson(args: LuaThread.ArgStack): Int {
} }
fun provideUtilityBindings(lua: LuaThread) { fun provideUtilityBindings(lua: LuaThread) {
with(lua) { lua.push {
push {
LuaThread.LOGGER.info(it.nextString()) LuaThread.LOGGER.info(it.nextString())
0 0
} }
storeGlobal("__print") lua.storeGlobal("__print")
push { lua.push {
LuaThread.LOGGER.warn(it.nextString()) LuaThread.LOGGER.warn(it.nextString())
0 0
} }
storeGlobal("__print_warn") lua.storeGlobal("__print_warn")
push { lua.push {
LuaThread.LOGGER.error(it.nextString()) LuaThread.LOGGER.error(it.nextString())
0 0
} }
storeGlobal("__print_error") lua.storeGlobal("__print_error")
push { lua.push {
LuaThread.LOGGER.fatal(it.nextString()) LuaThread.LOGGER.fatal(it.nextString())
0 0
} }
storeGlobal("__print_fatal") lua.storeGlobal("__print_fatal")
push { lua.push {
val path = it.nextString() val path = it.nextString()
try { try {
load(Starbound.readLuaScript(path).join(), "@$path") it.lua.load(Starbound.readLuaScript(path).join(), "@$path")
1 1
} catch (err: Exception) { } catch (err: Exception) {
LuaThread.LOGGER.error("Exception loading Lua script $path", err) LuaThread.LOGGER.error("Exception loading Lua script $path", err)
@ -201,49 +200,48 @@ fun provideUtilityBindings(lua: LuaThread) {
} }
} }
storeGlobal("__require") lua.storeGlobal("__require")
push { lua.push {
push(random.nextDouble()) it.lua.push(it.lua.random.nextDouble())
1 1
} }
storeGlobal("__random_double") lua.storeGlobal("__random_double")
push { lua.push {
push(random.nextLong(it.nextLong(), it.nextLong())) it.lua.push(it.lua.random.nextLong(it.nextLong(), it.nextLong()))
1 1
} }
storeGlobal("__random_long") lua.storeGlobal("__random_long")
push { lua.push {
random = random(it.nextLong()) it.lua.random = random(it.nextLong())
0 0
} }
storeGlobal("__random_seed") lua.storeGlobal("__random_seed")
push { lua.push {
push(it.lua.getNamedHandle(it.nextString())) it.lua.push(it.lua.getNamedHandle(it.nextString()))
1 1
} }
storeGlobal("gethandle") lua.storeGlobal("gethandle")
push { lua.push {
val find = it.lua.findNamedHandle(it.nextString()) val find = it.lua.findNamedHandle(it.nextString())
if (find == null) { if (find == null) {
0 0
} else { } else {
push(find) it.lua.push(find)
1 1
} }
} }
storeGlobal("findhandle") lua.storeGlobal("findhandle")
}
lua.pushTable() lua.pushTable()
lua.dup() lua.dup()

View File

@ -943,6 +943,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaThread) {
lua.pop() lua.pop()
lua.load(worldScript, "@/internal/world.lua") lua.call {
lua.call() load(worldScript, "@/internal/world.lua")
}
} }

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.EntityType
@ -96,7 +97,17 @@ private data class CallScriptData(
private fun LuaThread.ArgStack.getScriptData(): CallScriptData? { private fun LuaThread.ArgStack.getScriptData(): CallScriptData? {
if (peek() == LuaType.STRING) { if (peek() == LuaType.STRING) {
return CallScriptData(nextString(), nextJson().asJsonArray, nextJson()) val name = nextString()
val nextJson = nextJson()
val expected = nextJson()
if (nextJson is JsonObject && nextJson.size() == 0) {
return CallScriptData(name, JsonArray(), expected)
} else if (nextJson is JsonArray) {
return CallScriptData(name, nextJson, expected)
} else {
throw IllegalArgumentException("Invalid script arguments to use (expected to be an array): $nextJson")
}
} else { } else {
skip(3) skip(3)
return null return null

View File

@ -29,7 +29,7 @@ private fun name(self: WorldObject, args: LuaThread.ArgStack): Int {
return 1 return 1
} }
private fun directions(self: WorldObject, args: LuaThread.ArgStack): Int { private fun direction(self: WorldObject, args: LuaThread.ArgStack): Int {
args.lua.push(self.direction.numericalValue) args.lua.push(self.direction.numericalValue)
return 1 return 1
} }
@ -320,7 +320,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaThread) {
lua.storeGlobal("object") lua.storeGlobal("object")
lua.pushBinding(self, "name", ::name) lua.pushBinding(self, "name", ::name)
lua.pushBinding(self, "directions", ::directions) lua.pushBinding(self, "direction", ::direction)
lua.pushBinding(self, "position", ::position) lua.pushBinding(self, "position", ::position)
lua.pushBinding(self, "setInteractive", ::setInteractive) lua.pushBinding(self, "setInteractive", ::setInteractive)
lua.pushBinding(self, "uniqueId", ::uniqueId) lua.pushBinding(self, "uniqueId", ::uniqueId)

View File

@ -29,7 +29,7 @@ private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Ma
if (parameter.key != null) if (parameter.key != null)
str = parameter.key str = parameter.key
// original engine does this, and i don't know why this make any sense // FIXME: original engine does this, and i don't know why this make any sense
else if (parameter.value is JsonPrimitive && parameter.value.isString) else if (parameter.value is JsonPrimitive && parameter.value.isString)
str = parameter.value.asString str = parameter.value.asString
@ -144,7 +144,12 @@ private fun createNode(
functions.add(name) functions.add(name)
val outputConfig = data.get("output") { JsonObject() } val outputConfig = data.get("output") { JsonObject() }
val output = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.output) val node = Registries.behaviorNodes.getOrThrow(name).value
val output = LinkedHashMap(node.output)
// original engine doesn't do this
if (node.script != null)
scripts.add(node.script.fullPath)
for ((k, v) in output.entries) { for ((k, v) in output.entries) {
val replaced = replaceOutputBehaviorTag(outputConfig[k]?.asString ?: v.key, treeParameters) val replaced = replaceOutputBehaviorTag(outputConfig[k]?.asString ?: v.key, treeParameters)
@ -154,14 +159,14 @@ private fun createNode(
} }
} }
check(lua.loadGlobal("ActionNode") == LuaType.FUNCTION) { "Global ActionNode is not a Lua function" } val handle = lua.call(1) {
lua.push(name) check(loadGlobal("ActionNode") == LuaType.FUNCTION) { "Global ActionNode is not a Lua function" }
push(lua, parameters) push(name)
push(lua, output) push(this, parameters)
lua.call(3, 1) push(this, output)
val handle = lua.createHandle() }[0]
handles.add(handle) handles.add(handle)
lua.pop()
return handle return handle
} }
@ -169,15 +174,14 @@ private fun createNode(
functions.add(name) functions.add(name)
val sacrifice = createNode(lua, data["child"] as JsonObject, treeParameters, blackboard, scripts, functions, handles) val sacrifice = createNode(lua, data["child"] as JsonObject, treeParameters, blackboard, scripts, functions, handles)
check(lua.loadGlobal("DecoratorNode") == LuaType.FUNCTION) { "Global DecoratorNode is not a Lua function" } val handle = lua.call(1) {
lua.push(name) check(loadGlobal("DecoratorNode") == LuaType.FUNCTION) { "Global DecoratorNode is not a Lua function" }
push(lua, parameters) push(name)
lua.push(sacrifice) push(this, parameters)
lua.call(3, 1) push(sacrifice)
val handle = lua.createHandle() }[0]
handles.add(handle)
lua.pop()
handles.add(handle)
return handle return handle
} }
@ -189,21 +193,20 @@ private fun createNode(
val factory = CompositeNodeType.entries.valueOf(name) val factory = CompositeNodeType.entries.valueOf(name)
check(lua.loadGlobal(factory.fnName) == LuaType.FUNCTION) { "Global ${factory.fnName} is not a Lua function" } val handle = lua.call(1) {
push(lua, parameters) check(loadGlobal(factory.fnName) == LuaType.FUNCTION) { "Global ${factory.fnName} is not a Lua function" }
push(this, parameters)
lua.pushTable(children.size) pushTable(children.size)
for ((i, child) in children.withIndex()) { for ((i, child) in children.withIndex()) {
lua.push(i + 1L) push(i + 1L)
lua.push(child) push(child)
lua.setTableValue() setTableValue()
} }
}[0]
lua.call(2, 1)
val handle = lua.createHandle()
handles.add(handle) handles.add(handle)
lua.pop()
return handle return handle
} }
@ -212,11 +215,7 @@ private fun createNode(
} }
private fun createBlackboard(lua: LuaThread): LuaHandle { private fun createBlackboard(lua: LuaThread): LuaHandle {
lua.loadGlobal("Blackboard") return lua.call(1) { lua.loadGlobal("Blackboard") }[0]
lua.call(numResults = 1)
val handle = lua.createHandle()
lua.pop()
return handle
} }
private fun createBehaviorTree(args: LuaThread.ArgStack): Int { private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
@ -269,19 +268,36 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
} }
handles.add(blackboard) handles.add(blackboard)
args.lua.ensureExtraCapacity(40) args.lua.ensureExtraCapacity(40)
mergedParams.scripts.forEach { scripts.add(it.fullPath) }
val root = createNode(args.lua, mergedParams.root, mergedParams.mappedParameters, blackboard, scripts, functions, handles) val root = createNode(args.lua, mergedParams.root, mergedParams.mappedParameters, blackboard, scripts, functions, handles)
handles.add(root) handles.add(root)
check(args.lua.loadGlobal("BehaviorState") == LuaType.FUNCTION) { "Global BehaviorState is not a Lua function" } scripts.forEach {
args.lua.call {
loadGlobal("require")
push(it)
}
}
args.lua.push(blackboard) val handle = args.lua.call(1) {
args.lua.push(root) check(loadGlobal("BehaviorState") == LuaType.FUNCTION) { "Global BehaviorState is not a Lua function" }
args.lua.call(2, 1) push(blackboard)
push(root)
}[0]
args.lua.call {
handle.push(this)
push("bake")
check(loadTableValue() == LuaType.FUNCTION) { "BehaviorTree.bake is not a Lua function" }
swap(-2, -1)
}
args.lua.push(handle)
handle.close()
handles.forEach { it.close() } handles.forEach { it.close() }
return 1 return 1
} }
@ -297,6 +313,7 @@ fun provideBehaviorBindings(lua: LuaThread) {
lua.pop() lua.pop()
lua.load(script, "@/internal/behavior.lua") lua.call {
lua.call() load(script, "@/internal/behavior.lua")
}
} }

View File

@ -97,10 +97,12 @@ class LegacyWireProcessor(val world: ServerWorld) {
if (newState != node.state) { if (newState != node.state) {
try { try {
node.state = newState node.state = newState
entity.lua.pushTable(hashSize = 2)
entity.lua.setTableValue("node", i) entity.lua.invokeGlobal("onInputNodeChange") {
entity.lua.setTableValue("level", newState) pushTable(hashSize = 2)
entity.lua.invokeGlobal("onInputNodeChange", 1) setTableValue("node", i)
setTableValue("level", newState)
}
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err) LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err)
} }

View File

@ -51,6 +51,7 @@ import ru.dbotthepony.kstarbound.world.api.MutableTileState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
import ru.dbotthepony.kstarbound.world.entities.NPCEntity
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -232,6 +233,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
for (obj in world.storage.loadEntities(pos).await()) { for (obj in world.storage.loadEntities(pos).await()) {
try { try {
if (obj !is NPCEntity)
obj.joinWorld(world) obj.joinWorld(world)
} catch (err: Exception) { } catch (err: Exception) {
LOGGER.error("Exception while spawning entity $obj in world", err) LOGGER.error("Exception while spawning entity $obj in world", err)

View File

@ -1,9 +1,17 @@
package ru.dbotthepony.kstarbound.server.world package ru.dbotthepony.kstarbound.server.world
import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Caffeine
import com.github.luben.zstd.Zstd
import com.github.luben.zstd.ZstdDictCompress
import com.github.luben.zstd.ZstdDictDecompress
import com.github.luben.zstd.ZstdException
import com.github.luben.zstd.ZstdInputStreamNoFinalizer
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.ints.IntArraySet import it.unimi.dsi.fastutil.ints.IntArraySet
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.ObjectArrayList import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -12,6 +20,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.JsonArrayCollector import ru.dbotthepony.kommons.gson.JsonArrayCollector
import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
@ -48,10 +57,12 @@ import ru.dbotthepony.kstarbound.world.UniversePos
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.lang.ref.Cleaner.Cleanable import java.lang.ref.Cleaner.Cleanable
import java.nio.ByteBuffer
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.sql.PreparedStatement import java.sql.PreparedStatement
import java.sql.ResultSet import java.sql.ResultSet
import java.time.Duration
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -112,9 +123,17 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
`z` INTEGER NOT NULL, `z` INTEGER NOT NULL,
`parameters` BLOB NOT NULL, `parameters` BLOB NOT NULL,
`planets` BLOB NOT NULL, `planets` BLOB NOT NULL,
`dictionary` INTEGER NOT NULL,
PRIMARY KEY(`x`, `y`, `z`) PRIMARY KEY(`x`, `y`, `z`)
) )
""".trimIndent()) """.trimIndent())
it.execute("""
CREATE TABLE IF NOT EXISTS `dictionary` (
`version` INTEGER NOT NULL PRIMARY KEY,
`data` BLOB NOT NULL
)
""".trimIndent())
} }
database.autoCommit = false database.autoCommit = false
@ -125,10 +144,13 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
private val scope = CoroutineScope(ScheduledCoroutineExecutor(carrier) + SupervisorJob()) private val scope = CoroutineScope(ScheduledCoroutineExecutor(carrier) + SupervisorJob())
private val selectChunk = database.prepareStatement("SELECT `systems`, `constellations` FROM `chunk` WHERE `x` = ? AND `y` = ?") private val selectChunk = database.prepareStatement("SELECT `systems`, `constellations` FROM `chunk` WHERE `x` = ? AND `y` = ?")
private val selectSystem = database.prepareStatement("SELECT `parameters`, `planets` FROM `system` WHERE `x` = ? AND `y` = ? AND `z` = ?") private val selectSystem = database.prepareStatement("SELECT `parameters`, `planets`, `dictionary` FROM `system` WHERE `x` = ? AND `y` = ? AND `z` = ?")
private val selectDictionary = database.prepareStatement("SELECT `data` FROM `dictionary` WHERE `version` = ?")
private val selectSamples = database.prepareStatement("SELECT `x`, `y`, `z`, `parameters`, `planets` FROM `system` WHERE `dictionary` = ? ORDER BY RANDOM() LIMIT 2000")
private val insertChunk = database.prepareStatement("INSERT INTO `chunk` (`x`, `y`, `systems`, `constellations`) VALUES (?, ?, ?, ?)") private val insertChunk = database.prepareStatement("INSERT INTO `chunk` (`x`, `y`, `systems`, `constellations`) VALUES (?, ?, ?, ?)")
private val insertSystem = database.prepareStatement("INSERT INTO `system` (`x`, `y`, `z`, `parameters`, `planets`) VALUES (?, ?, ?, ?, ?)") private val insertSystem = database.prepareStatement("REPLACE INTO `system` (`x`, `y`, `z`, `parameters`, `planets`, `dictionary`) VALUES (?, ?, ?, ?, ?, ?)")
private val insertDictionary = database.prepareStatement("INSERT INTO `dictionary` (`version`, `data`) VALUES (?, ?)")
private class SerializedChunk(val x: Int, val y: Int, val systems: ByteArray, val constellations: ByteArray) { private class SerializedChunk(val x: Int, val y: Int, val systems: ByteArray, val constellations: ByteArray) {
fun write(statement: PreparedStatement) { fun write(statement: PreparedStatement) {
@ -164,19 +186,16 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
}.collect(JsonArrayCollector).writeJsonArrayZstd(4) }.collect(JsonArrayCollector).writeJsonArrayZstd(4)
) )
} }
fun write(statement: PreparedStatement) {
serialize().write(statement)
}
} }
private class SerializedSystem(val x: Int, val y: Int, val z: Int, val parameters: ByteArray, val planets: ByteArray) { private class SerializedSystem(val x: Int, val y: Int, val z: Int, val parameters: ByteArray, val planets: ByteArray, val dictionary: Int) {
fun write(statement: PreparedStatement) { fun write(statement: PreparedStatement) {
statement.setInt(1, x) statement.setInt(1, x)
statement.setInt(2, y) statement.setInt(2, y)
statement.setInt(3, z) statement.setInt(3, z)
statement.setBytes(4, parameters) statement.setBytes(4, parameters)
statement.setBytes(5, planets) statement.setBytes(5, planets)
statement.setInt(6, dictionary)
statement.execute() statement.execute()
} }
@ -191,19 +210,16 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
} }
} }
fun serialize(): SerializedSystem { fun serialize(dict: Dicts? = null): SerializedSystem {
return SerializedSystem( return SerializedSystem(
x, y, z, x, y, z,
Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(8), Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(6, dictionary = dict?.compress),
planets.entries.stream() planets.entries.stream()
.map { jsonArrayOf(it.key.first, it.key.second, it.value) } .map { jsonArrayOf(it.key.first, it.key.second, it.value) }
.collect(JsonArrayCollector).writeJsonArrayZstd(8) .collect(JsonArrayCollector).writeJsonArrayZstd(6, dictionary = dict?.compress),
dict?.version ?: 0
) )
} }
fun write(statement: PreparedStatement) {
serialize().write(statement)
}
} }
// first, chunks in process of loading/generating must not be evicted // first, chunks in process of loading/generating must not be evicted
@ -254,6 +270,239 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
.executor(Starbound.EXECUTOR) .executor(Starbound.EXECUTOR)
.build<Vector3i, CompletableFuture<System?>>() .build<Vector3i, CompletableFuture<System?>>()
private class Dicts(val version: Int, bytesParameters: ByteArray) : Closeable {
private var compressL: Lazy<ZstdDictCompress>? = lazy(LazyThreadSafetyMode.NONE) { ZstdDictCompress(bytesParameters, 6) }
private var decompressL: Lazy<ZstdDictDecompress>? = lazy(LazyThreadSafetyMode.NONE) { ZstdDictDecompress(bytesParameters) }
val compress get() = compressL!!.value
val decompress get() = decompressL!!.value
override fun close() {
/*if (compressL?.isInitialized() == true) {
compressL!!.value.close()
compressL = null
}
if (decompressL?.isInitialized() == true) {
decompressL!!.value.close()
decompressL = null
}*/
// zstd dicts use finalizers
// while we could close the dicts explicitly, they might be still in use
// due to off-thread main thread compression being in-process
compressL = null
decompressL = null
}
}
private val dictionaryCache = Caffeine.newBuilder()
.maximumSize(32L)
.expireAfterAccess(Duration.ofMinutes(5))
.executor(Starbound.EXECUTOR)
.evictionListener<Int, KOptional<Dicts>> { _, value, _ -> value?.orNull()?.close() }
.build<Int, KOptional<Dicts>>()
private fun loadDictionary(id: Int): Dicts? {
return dictionaryCache.get(id) { v ->
selectDictionary.setInt(1, v)
selectDictionary.executeQuery().use {
if (it.next()) {
val bytes = decompress(it.getBytes(1), null)
return@get KOptional(Dicts(v, bytes))
} else {
return@get KOptional()
}
}
}.orNull()
}
private var latestDictionary = 0
private var latestDictionaryCapacity = 0
private var dictionary: Dicts? = null
init {
database.createStatement().use {
it.executeQuery("""
SELECT MAX(`version`) FROM `dictionary`
""".trimIndent()).use {
it.next()
// if dictionary table is empty, max() returns null, which gets treated as 0 by getInt
latestDictionary = it.getInt(1)
}
it.executeQuery("""
SELECT COUNT(*) FROM `dictionary` WHERE `version` = $latestDictionary
""".trimIndent()).use {
it.next()
latestDictionaryCapacity = it.getInt(1)
}
}
if (latestDictionary != 0) {
dictionary = loadDictionary(latestDictionary)!!
}
}
private fun decompress(input: ByteArray, dictionary: ZstdDictDecompress?): ByteArray {
val parts = ArrayList<ByteArray>()
val stream = ZstdInputStreamNoFinalizer(FastByteArrayInputStream(input))
if (dictionary != null)
stream.setDict(dictionary)
while (true) {
val alloc = ByteArray(1024 * 16)
val read = stream.read(alloc)
if (read <= 0) {
break
} else if (read == alloc.size) {
parts.add(alloc)
} else {
parts.add(alloc.copyOf(read))
}
}
stream.close()
val output = ByteArray(parts.sumOf { it.size })
var i = 0
for (part in parts) {
java.lang.System.arraycopy(part, 0, output, i, part.size)
i += part.size
}
return output
}
private fun recompress(input: ByteArray, dict: ZstdDictCompress): ByteArray {
val stream = FastByteArrayOutputStream()
ZstdOutputStreamNoFinalizer(stream, 6).use {
it.setDict(dict)
it.write(input)
}
return stream.array.copyOf(stream.length)
}
private fun recompress(input: ByteArray): ByteArray {
val stream = FastByteArrayOutputStream()
ZstdOutputStreamNoFinalizer(stream, 6).use {
it.write(input)
}
return stream.array.copyOf(stream.length)
}
private fun dictionaryMaintenance() {
val limit = if (latestDictionary == 0) 200 else 2000
if (latestDictionaryCapacity >= limit) {
LOGGER.info("Optimizing star map compression algorithm, star map might become unresponsive for a moment...")
class Tuple(val x: Int, val y: Int, val z: Int, parameters: ByteArray, planets: ByteArray) {
val parameters = Starbound.EXECUTOR.supplyAsync { decompress(parameters, dictionary?.decompress) }
val planets = Starbound.EXECUTOR.supplyAsync { decompress(planets, dictionary?.decompress) }
}
// current dictionary is old enough,
// create new one to adapt to possible data changes e.g. due to added or removed mods
// collect samples
val samples = ArrayList<Tuple>()
selectSamples.setInt(1, latestDictionary)
selectSamples.executeQuery().use {
while (it.next()) {
samples.add(Tuple(it.getInt(1), it.getInt(2), it.getInt(3), it.getBytes(4), it.getBytes(5)))
}
}
// wait for samples to be decompressed
val sampleBuffer = ByteBuffer.allocateDirect(samples.sumOf { it.parameters.join().size + it.planets.join().size })
for (sample in samples) {
sampleBuffer.put(sample.parameters.join())
sampleBuffer.put(sample.planets.join())
}
sampleBuffer.position(0)
// create dictionary
val buffer = ByteBuffer.allocateDirect(1024 * 1024 * 4) // up to 4 MiB dictionary (before compression, since dedicated zstd dictionaries are not compressed)
// 4 MiB seems to be sweet spot, dictionary isn't that big (smaller indices to reference inside dictionary),
// takes small amount of space, and training is done moderately fast
// Too big dictionaries cause over-fitting and generally *reduce* compression ratio,
// while too small dictionaries don't contain enough data to be effective
val status = Zstd.trainFromBufferDirect(
sampleBuffer,
IntArray(samples.size) { samples[it].parameters.join().size + samples[it].planets.join().size },
buffer,
false, 6
)
if (Zstd.isError(status)) {
throw ZstdException(status)
}
val copyBytes = ByteArray(status.toInt())
buffer.position(0)
buffer.get(copyBytes)
val dicts = Dicts(++latestDictionary, copyBytes)
dictionary = dicts
dictionaryCache.put(latestDictionary, KOptional(dicts))
insertDictionary.setInt(1, latestDictionary)
insertDictionary.setBytes(2, recompress(copyBytes))
insertDictionary.execute()
database.commit()
latestDictionaryCapacity = 0
LOGGER.info("Star map compression optimized")
if (latestDictionary == 1) {
LOGGER.info("Recompressing star map chunks with new dictionary...")
// previous data wasn't compressed by any dictionary, so let's recompress it with new dictionary
val recompressed = ArrayList<CompletableFuture<Triple<Tuple, ByteArray, ByteArray>>>()
for (tuple in samples) {
recompressed.add(
Starbound.EXECUTOR.supplyAsync {
Triple(tuple, recompress(tuple.parameters.join(), dicts.compress), recompress(tuple.planets.join(), dicts.compress))
}
)
}
try {
for ((tuple, parameters, planets) in recompressed.map { it.join() }) {
insertSystem.setInt(1, tuple.x)
insertSystem.setInt(2, tuple.y)
insertSystem.setInt(3, tuple.z)
insertSystem.setBytes(4, parameters)
insertSystem.setBytes(5, planets)
insertSystem.setInt(6, 1)
insertSystem.execute()
}
database.commit()
} catch (err: Throwable) {
database.rollback()
throw err
}
LOGGER.info("Recompressed star map chunks with new dictionary")
}
}
}
private fun loadSystem(pos: Vector3i): CompletableFuture<System>? { private fun loadSystem(pos: Vector3i): CompletableFuture<System>? {
selectSystem.setInt(1, pos.x) selectSystem.setInt(1, pos.x)
selectSystem.setInt(2, pos.y) selectSystem.setInt(2, pos.y)
@ -263,12 +512,21 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
if (it.next()) { if (it.next()) {
val parametersBytes = it.getBytes(1) val parametersBytes = it.getBytes(1)
val planetsBytes = it.getBytes(2) val planetsBytes = it.getBytes(2)
val dictionaryVersion = it.getInt(3)
val dict: ZstdDictDecompress?
if (dictionaryVersion == 0) {
dict = null
} else {
dict = loadDictionary(dictionaryVersion)?.decompress
}
// deserialize in off-thread since it involves big json structures // deserialize in off-thread since it involves big json structures
Starbound.EXECUTOR.supplyAsync { Starbound.EXECUTOR.supplyAsync {
val parameters: CelestialParameters = Starbound.gson.fromJson(parametersBytes.readJsonElementZstd())!! val parameters: CelestialParameters = Starbound.gson.fromJson(parametersBytes.readJsonElementZstd(dictionary = dict))!!
val planets: Map<Pair<Int, Int>, CelestialParameters> = planetsBytes.readJsonArrayZstd().associate { val planets: Map<Pair<Int, Int>, CelestialParameters> = planetsBytes.readJsonArrayZstd(dictionary = dict).associate {
it as JsonArray it as JsonArray
(it[0].asInt to it[1].asInt) to Starbound.gson.fromJson(it[2])!! (it[0].asInt to it[1].asInt) to Starbound.gson.fromJson(it[2])!!
} }
@ -521,6 +779,8 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
val random = random(staticRandom64(chunkPos.x, chunkPos.y, "ChunkIndexMix")) val random = random(staticRandom64(chunkPos.x, chunkPos.y, "ChunkIndexMix"))
val region = chunkRegion(chunkPos) val region = chunkRegion(chunkPos)
val dict = dictionary
return CompletableFuture.supplyAsync(Supplier { return CompletableFuture.supplyAsync(Supplier {
val constellationCandidates = ArrayList<Vector2i>() val constellationCandidates = ArrayList<Vector2i>()
val systemPositions = ArrayList<Vector3i>() val systemPositions = ArrayList<Vector3i>()
@ -534,7 +794,7 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
val system = generateSystem(random, pos) ?: continue val system = generateSystem(random, pos) ?: continue
systemPositions.add(pos) systemPositions.add(pos)
systems.add(CompletableFuture.supplyAsync(Supplier { system.serialize() }, Starbound.EXECUTOR)) systems.add(CompletableFuture.supplyAsync(Supplier { system.serialize(dict) }, Starbound.EXECUTOR))
systemCache.put(Vector3i(system.x, system.y, system.z), CompletableFuture.completedFuture(system)) systemCache.put(Vector3i(system.x, system.y, system.z), CompletableFuture.completedFuture(system))
if ( if (
@ -554,6 +814,13 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
serialized.write(insertChunk) serialized.write(insertChunk)
systems.forEach { it.get().write(insertSystem) } systems.forEach { it.get().write(insertSystem) }
database.commit() database.commit()
if (latestDictionary == (dict?.version ?: 0)) {
latestDictionaryCapacity += systems.size
// enqueue dictionary maintenance
carrier.execute(::dictionaryMaintenance)
}
chunk chunk
}, carrier) }, carrier)
}, Starbound.EXECUTOR).thenCompose { it } }, Starbound.EXECUTOR).thenCompose { it }
@ -738,4 +1005,8 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
} }
override val region: AABBi = AABBi(Vector2i(baseInformation.xyCoordRange.x, baseInformation.xyCoordRange.x), Vector2i(baseInformation.xyCoordRange.y, baseInformation.xyCoordRange.y)) override val region: AABBi = AABBi(Vector2i(baseInformation.xyCoordRange.x, baseInformation.xyCoordRange.x), Vector2i(baseInformation.xyCoordRange.y, baseInformation.xyCoordRange.y))
companion object {
private val LOGGER = LogManager.getLogger()
}
} }

View File

@ -122,12 +122,10 @@ abstract class SystemWorld(val location: Vector3i, val clock: JVMClock, val univ
} }
fun compatCoordinateSeed(coordinate: UniversePos, seedMix: String): Long { fun compatCoordinateSeed(coordinate: UniversePos, seedMix: String): Long {
// original code is utterly broken here // FIXME: original code is utterly broken here
// consider the following: // consider the following:
// auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0; // auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0;
// auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0; // auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0;
// first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0 // first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0
// this "coalesces" planet orbit into either 0 or 1 // this "coalesces" planet orbit into either 0 or 1
// then, we have coordinate.parent().orbitNumber(), which is correct, but only if we are orbiting a satellite // then, we have coordinate.parent().orbitNumber(), which is correct, but only if we are orbiting a satellite

View File

@ -333,13 +333,13 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
// TODO: hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit maybe? // TODO: hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit maybe?
if (totalDamage > 0.0) { if (totalDamage > 0.0) {
lua.pushTable(hashSize = 4) lua.invokeGlobal("damage") {
lua.setTableValue("sourceId", damage.request.sourceEntityId) pushTable(hashSize = 4)
lua.setTableValue("damage", totalDamage) setTableValue("sourceId", damage.request.sourceEntityId)
lua.setTableValue("sourceDamage", damage.request.damage) setTableValue("damage", totalDamage)
lua.setTableValue("sourceKind", damage.request.damageSourceKind) setTableValue("sourceDamage", damage.request.damage)
setTableValue("sourceKind", damage.request.damageSourceKind)
lua.invokeGlobal("damage", 1) }
} }
if (health <= 0.0) { if (health <= 0.0) {
@ -350,7 +350,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
} }
private val shouldDie: Boolean get() { private val shouldDie: Boolean get() {
val result = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false) val result = lua.invokeGlobal("shouldDie", 1, {}, { getBoolean() == true }).orElse(false)
return result || health <= 0.0 //|| lua.errorState return result || health <= 0.0 //|| lua.errorState
} }

View File

@ -332,13 +332,13 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
val totalDamage = notifications.sumOf { it.healthLost } val totalDamage = notifications.sumOf { it.healthLost }
if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) { if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) {
lua.pushTable(hashSize = 4) lua.invokeGlobal("damage") {
lua.setTableValue("sourceId", damage.request.sourceEntityId) pushTable(hashSize = 4)
lua.setTableValue("damage", totalDamage) setTableValue("sourceId", damage.request.sourceEntityId)
lua.setTableValue("sourceDamage", damage.request.damage) setTableValue("damage", totalDamage)
lua.setTableValue("sourceKind", damage.request.damageSourceKind) setTableValue("sourceDamage", damage.request.damage)
setTableValue("sourceKind", damage.request.damageSourceKind)
lua.invokeGlobal("damage", 1) }
} }
return notifications return notifications
@ -407,7 +407,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
remove(RemovalReason.DYING) remove(RemovalReason.DYING)
return return
} else { } else {
val shouldDie = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false) val shouldDie = lua.invokeGlobal("shouldDie", 1, {}, { getBoolean() == true }).orElse(false)
if (shouldDie) { if (shouldDie) {
remove(RemovalReason.DYING) remove(RemovalReason.DYING)
@ -425,7 +425,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
super.onRemove(world, reason) super.onRemove(world, reason)
if (isLocal) if (isLocal)
lua.invokeGlobal("die", 0) lua.invokeGlobal("die")
val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() } val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() }

View File

@ -191,7 +191,6 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
fun experienceDamage(damage: DamageData): List<DamageNotification> { fun experienceDamage(damage: DamageData): List<DamageNotification> {
val results = lua.invokeGlobal("applyDamageRequest", 1, { val results = lua.invokeGlobal("applyDamageRequest", 1, {
push(Starbound.gson.toJsonTree(damage)) push(Starbound.gson.toJsonTree(damage))
1
}, { getJson() as? JsonArray }).flatMap { KOptional.ofNullable(it) } }, { getJson() as? JsonArray }).flatMap { KOptional.ofNullable(it) }
if (results.isPresent) { if (results.isPresent) {
@ -632,7 +631,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
fun remove() { fun remove() {
if (entity.isLocal) if (entity.isLocal)
lua.invokeGlobal("onExpire", 0) lua.invokeGlobal("onExpire")
uniqueEffectMetadata.remove(metadataNetworkID) uniqueEffectMetadata.remove(metadataNetworkID)

View File

@ -306,13 +306,13 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
fun addConnection(connection: WireConnection) { fun addConnection(connection: WireConnection) {
if (connection !in connectionsInternal) { if (connection !in connectionsInternal) {
connectionsInternal.add(connection.copy()) connectionsInternal.add(connection.copy())
lua.invokeGlobal("onNodeConnectionChange", 0) lua.invokeGlobal("onNodeConnectionChange")
} }
} }
fun removeConnection(connection: WireConnection) { fun removeConnection(connection: WireConnection) {
if (connectionsInternal.remove(connection)) { if (connectionsInternal.remove(connection)) {
lua.invokeGlobal("onNodeConnectionChange", 0) lua.invokeGlobal("onNodeConnectionChange")
} }
} }
@ -325,20 +325,20 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index } val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index }
if (any == true) { if (any == true) {
otherEntity!!.lua.invokeGlobal("onNodeConnectionChange", 0) otherEntity!!.lua.invokeGlobal("onNodeConnectionChange")
} }
any == true any == true
} }
if (any) if (any)
lua.invokeGlobal("onNodeConnectionChange", 0) lua.invokeGlobal("onNodeConnectionChange")
} }
} }
fun removeConnectionsTo(pos: Vector2i) { fun removeConnectionsTo(pos: Vector2i) {
if (connectionsInternal.removeIf { it.entityLocation == pos }) { if (connectionsInternal.removeIf { it.entityLocation == pos }) {
lua.invokeGlobal("onNodeConnectionChange", 0) lua.invokeGlobal("onNodeConnectionChange")
} }
} }
@ -599,8 +599,6 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
setTableValue("source", diff) setTableValue("source", diff)
setTableValue("sourceId", request.source) setTableValue("sourceId", request.source)
1
}, { getJson() ?: JsonNull.INSTANCE }) }, { getJson() ?: JsonNull.INSTANCE })
if (result.isPresent) { if (result.isPresent) {
@ -675,7 +673,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
// break connection if other entity got removed // break connection if other entity got removed
if (connection.otherEntity?.removalReason?.removal == true) { if (connection.otherEntity?.removalReason?.removal == true) {
itr.remove() itr.remove()
lua.invokeGlobal("onNodeConnectionChange", 0) lua.invokeGlobal("onNodeConnectionChange")
continue continue
} }
@ -691,7 +689,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
// break connection if we point at invalid node // break connection if we point at invalid node
if (otherNode == null) { if (otherNode == null) {
itr.remove() itr.remove()
lua.invokeGlobal("onNodeConnectionChange", 0) lua.invokeGlobal("onNodeConnectionChange")
} else { } else {
newState = newState!! || otherNode.state newState = newState!! || otherNode.state
} }
@ -706,10 +704,12 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
// otherwise, keep current node state // otherwise, keep current node state
if (newState != null && node.state != newState) { if (newState != null && node.state != newState) {
node.state = newState node.state = newState
lua.pushTable(hashSize = 2)
lua.setTableValue("node", i) lua.invokeGlobal("onInputNodeChange") {
lua.setTableValue("level", newState) pushTable(hashSize = 2)
lua.invokeGlobal("onInputNodeChange", 1) setTableValue("node", i)
setTableValue("level", newState)
}
} }
} }
} }
@ -781,9 +781,10 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
} }
} }
if (!isRemote && reason.dying) { if (isLocal && reason.dying) {
lua.push(health <= 0.0) lua.invokeGlobal("die") {
lua.invokeGlobal("die", 1) push(health <= 0.0)
}
try { try {
if (doSmash) { if (doSmash) {

View File

@ -61,7 +61,7 @@ local function blackboardSet(self, t, key, value)
local mappings = self.vectorNumberInput[key] local mappings = self.vectorNumberInput[key]
if mappings then if mappings then
for _, pair in pairs(input) do for _, pair in pairs(mappings) do
local index = pair[1] local index = pair[1]
local tab = pair[2] local tab = pair[2]
tab[index] = value tab[index] = value
@ -107,12 +107,12 @@ function blackboardPrototype:parameters(parameters, nodeID)
if not typeInput then if not typeInput then
typeInput = {} typeInput = {}
self.input[i][pKey] = typeInput self.input[t][pKey] = typeInput
end end
table.insert(typeInput, {parameterName, tab}) table.insert(typeInput, {parameterName, tab})
tab[parameterName] = self.board[t][pKey] tab[parameterName] = self.board[t][pKey]
elseif pValue then elseif pValue ~= nil then
if t == 4 then -- vec2 if t == 4 then -- vec2
-- dumb special case for allowing a vec2 of blackboard number keys -- dumb special case for allowing a vec2 of blackboard number keys
if type(pValue) ~= 'table' then if type(pValue) ~= 'table' then
@ -132,7 +132,7 @@ function blackboardPrototype:parameters(parameters, nodeID)
end end
table.insert(typeInput, {i, vector}) table.insert(typeInput, {i, vector})
vector[i] = self.board[5][key] -- number vector[i] = self.board[5][vValue] -- number
else else
vector[i] = vValue vector[i] = vValue
end end
@ -142,8 +142,6 @@ function blackboardPrototype:parameters(parameters, nodeID)
else else
tab[parameterName] = pValue tab[parameterName] = pValue
end end
else
error(string.format('parameter %s of type %s for node %s has no key nor value', parameterName, parameter.type, nodeID))
end end
end end
@ -183,7 +181,7 @@ function blackboardPrototype:clearEphemerals(ephemerals)
end end
end end
local function Blackboard() function Blackboard()
return setmetatable({}, blackboardPrototype):ctor() return setmetatable({}, blackboardPrototype):ctor()
end end
@ -204,6 +202,19 @@ local function runAndReset(self, ...)
return status return status
end end
local function reconstructTree(stack)
local top = #stack
if top == 0 then return '' end
local result = {'\nbehavior tree traceback:'}
for i = top, 1, -1 do
table.insert(result, string.format('%s%d. - %q', string.rep(' ', top - i + 1), top - i + 1, stack[i]))
end
return table.concat(result, '\n')
end
-- ActionNode -- ActionNode
local actionNode = {} local actionNode = {}
@ -221,10 +232,10 @@ function actionNode:ctor(name, parameters, outputs)
end end
function actionNode:bake() function actionNode:bake()
self.callable = _G[self.name] self.callable = _ENV[self.name]
if type(callable) ~= 'function' then if type(self.callable) ~= 'function' then
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable)) error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable))
end end
end end
@ -233,7 +244,8 @@ do
local resume = coroutine.resume local resume = coroutine.resume
local status = coroutine.status local status = coroutine.status
function actionNode:run(delta, blackboard) function actionNode:run(delta, blackboard, stack)
--table.insert(stack, self.name)
self.calls = self.calls + 1 self.calls = self.calls + 1
local status, nodeStatus, nodeExtra local status, nodeStatus, nodeExtra
@ -246,11 +258,13 @@ do
end end
if not status then if not status then
sb.logError('Behavior ActionNode %q failed: %s', self.name, nodeStatus) sb.logError(debug.traceback(self.coroutine, string.format('Behavior ActionNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack))))
--table.remove(stack)
return FAILURE return FAILURE
end end
if result == nil then if nodeStatus == nil then
--table.remove(stack)
return RUNNING return RUNNING
end end
@ -258,6 +272,8 @@ do
blackboard:setOutput(self, nodeExtra) blackboard:setOutput(self, nodeExtra)
end end
--table.remove(stack)
if nodeStatus then if nodeStatus then
return SUCCESS return SUCCESS
else else
@ -297,10 +313,10 @@ function decoratorNode:ctor(name, parameters, child)
end end
function decoratorNode:bake() function decoratorNode:bake()
self.callable = _G[self.name] self.callable = _ENV[self.name]
if type(callable) ~= 'function' then if type(self.callable) ~= 'function' then
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable)) error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable))
end end
self.child:bake() self.child:bake()
@ -311,7 +327,8 @@ do
local resume = coroutine.resume local resume = coroutine.resume
local coroutine_status = coroutine.status local coroutine_status = coroutine.status
function decoratorNode:run(delta, blackboard) function decoratorNode:run(delta, blackboard, stack)
--table.insert(stack, self.name)
self.calls = self.calls + 1 self.calls = self.calls + 1
if not self.coroutine then if not self.coroutine then
@ -320,7 +337,8 @@ do
local status, nodeStatus = resume(coroutine, parameters, blackboard, self.nodeID, delta) local status, nodeStatus = resume(coroutine, parameters, blackboard, self.nodeID, delta)
if not status then if not status then
sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus) sb.logError(debug.traceback(coroutine, string.format('Behavior DecoratorNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack))))
--table.remove(stack)
return FAILURE return FAILURE
end end
@ -329,38 +347,44 @@ do
if s == 'dead' then if s == 'dead' then
-- quite unexpected, but whatever -- quite unexpected, but whatever
--table.remove(stack)
return SUCCESS return SUCCESS
else else
self.coroutine = coroutine self.coroutine = coroutine
end end
elseif nodeStatus then elseif nodeStatus then
--table.remove(stack)
return SUCCESS return SUCCESS
else else
--table.remove(stack)
return FAILURE return FAILURE
end end
end end
while true do while true do
local childStatus = runAndReset(self.child, delta, blackboard) local childStatus = runAndReset(self.child, delta, blackboard, stack)
if childStatus == RUNNING then if childStatus == RUNNING then
table.remove(stack)
return RUNNING return RUNNING
end end
local status, nodeStatus = resume(self.coroutine, childStatus) local status, nodeStatus = resume(self.coroutine, childStatus)
if not status then if not status then
sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus) sb.logError(debug.traceback(coroutine, string.format('Behavior DecoratorNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack))))
--table.remove(stack)
return FAILURE return FAILURE
end end
if nodeStatus == nil then if nodeStatus == nil then
-- another yield OR unexpected return? -- another yield OR unexpected return?
local s = coroutine_status(coroutine) local s = coroutine_status(self.coroutine)
if s == 'dead' then if s == 'dead' then
self.coroutine = nil self.coroutine = nil
--table.remove(stack)
return SUCCESS return SUCCESS
end end
else else
@ -368,8 +392,10 @@ do
self.coroutine = nil self.coroutine = nil
if nodeStatus then if nodeStatus then
--table.remove(stack)
return SUCCESS return SUCCESS
else else
--table.remove(stack)
return FAILURE return FAILURE
end end
end end
@ -407,20 +433,29 @@ function seqNode:ctor(children, isSelector)
return self return self
end end
function seqNode:run(delta, blackboard) function seqNode:run(delta, blackboard, stack)
self.calls = self.calls + 1 self.calls = self.calls + 1
local size = self.size local size = self.size
local isSelector = self.isSelector local isSelector = self.isSelector
--[[if isSelector then
table.insert(stack, 'SelectorNode')
else
table.insert(stack, 'SequenceNode')
end]]
while self.index <= size do while self.index <= size do
local child = self.children[self.index] local child = self.children[self.index]
local status = runAndReset(child, delta, blackboard) local status = runAndReset(child, delta, blackboard, stack)
if status == RUNNING then if status == RUNNING then
--table.remove(stack)
return RUNNING return RUNNING
elseif isSelector and status == SUCCESS then elseif isSelector and status == SUCCESS then
--table.remove(stack)
return SUCCESS return SUCCESS
elseif not isSelector and status == FAILURE then elseif not isSelector and status == FAILURE then
--table.remove(stack)
return FAILURE return FAILURE
end end
@ -464,13 +499,13 @@ parallelNode.__index = parallelNode
function parallelNode:ctor(parameters, children) function parallelNode:ctor(parameters, children)
self.children = children self.children = children
if type(parameters.success) == 'number' then if type(parameters.success) == 'number' and parameters.success >= 0 then
self.successLimit = parameters.success self.successLimit = parameters.success
else else
self.successLimit = #children self.successLimit = #children
end end
if type(parameters.fail) == 'number' then if type(parameters.fail) == 'number' and parameters.fail >= 0 then
self.failLimit = parameters.fail self.failLimit = parameters.fail
else else
self.failLimit = #children self.failLimit = #children
@ -483,15 +518,17 @@ function parallelNode:ctor(parameters, children)
return self return self
end end
function parallelNode:run(delta, blackboard) function parallelNode:run(delta, blackboard, stack)
self.calls = self.calls + 1 self.calls = self.calls + 1
local failed = 0 local failed = 0
local succeeded = 0 local succeeded = 0
local failLimit = self.failLimit local failLimit = self.failLimit
local successLimit = self.successLimit local successLimit = self.successLimit
--table.insert(stack, 'ParallelNode')
for _, node in ipairs(self.children) do for _, node in ipairs(self.children) do
local status = runAndReset(node, delta, blackboard) local status = runAndReset(node, delta, blackboard, stack)
if status == SUCCESS then if status == SUCCESS then
succeeded = succeeded + 1 succeeded = succeeded + 1
@ -502,16 +539,19 @@ function parallelNode:run(delta, blackboard)
if failed >= failLimit then if failed >= failLimit then
self.lastFailed = failed self.lastFailed = failed
self.lastSucceed = succeeded self.lastSucceed = succeeded
--table.remove(stack)
return FAILURE return FAILURE
elseif succeeded >= successLimit then elseif succeeded >= successLimit then
self.lastFailed = failed self.lastFailed = failed
self.lastSucceed = succeeded self.lastSucceed = succeeded
--table.remove(stack)
return SUCCESS return SUCCESS
end end
end end
self.lastFailed = failed self.lastFailed = failed
self.lastSucceed = succeeded self.lastSucceed = succeeded
--table.remove(stack)
return RUNNING return RUNNING
end end
@ -550,11 +590,12 @@ function dynNode:ctor(children)
return self return self
end end
function dynNode:run(delta, blackboard) function dynNode:run(delta, blackboard, stack)
self.calls = self.calls + 1 self.calls = self.calls + 1
--table.insert(stack, 'DynamicNode')
for i, node in ipairs(self.children) do for i, node in ipairs(self.children) do
local status = runAndReset(node, delta, blackboard) local status = runAndReset(node, delta, blackboard, stack)
if stauts == FAILURE and self.index == i then if stauts == FAILURE and self.index == i then
self.index = self.index + 1 self.index = self.index + 1
@ -564,10 +605,12 @@ function dynNode:run(delta, blackboard)
end end
if status == SUCCESS or self.index > self.size then if status == SUCCESS or self.index > self.size then
--table.remove(stack)
return status return status
end end
end end
--table.remove(stack)
return RUNNING return RUNNING
end end
@ -605,7 +648,7 @@ function randNode:ctor(children)
return self return self
end end
function randNode:run(delta, blackboard) function randNode:run(delta, blackboard, stack)
self.calls = self.calls + 1 self.calls = self.calls + 1
if self.index == -1 and self.size ~= 0 then if self.index == -1 and self.size ~= 0 then
@ -615,7 +658,10 @@ function randNode:run(delta, blackboard)
if self.index == -1 then if self.index == -1 then
return FAILURE return FAILURE
else else
return runAndReset(self.children[self.index], delta, blackboard) --table.insert(stack, 'RandomNode')
local value = runAndReset(self.children[self.index], delta, blackboard, stack)
--table.remove(stack)
return value
end end
end end
@ -654,15 +700,20 @@ function statePrototype:ctor(blackboard, root)
end end
function statePrototype:run(delta) function statePrototype:run(delta)
local stack = {}
local ephemerals = self._blackboard:takeEphemerals() local ephemerals = self._blackboard:takeEphemerals()
local status = runAndReset(self.root, delta, self._blackboard) local status = runAndReset(self.root, delta, self._blackboard, stack)
self._blackboard:clearEphemerals(ephemerals) self._blackboard:clearEphemerals(ephemerals)
return status return status
end end
function statePrototype:clear() function statePrototype:clear()
self.tree:reset() self.root:reset()
end
function statePrototype:bake()
self.root:bake()
end end
function statePrototype:blackboard() function statePrototype:blackboard()

View File

@ -385,4 +385,72 @@ function mergeJson(base, with)
end end
end end
do
local line = ''
local function puts(f, ...)
line = line .. string.format(f, ...)
end
local function flush()
if line ~= '' then
sb.logInfo(line)
line = ''
end
end
local function printTable(input, level)
level = level or 0
if not next(input) then
puts('{ --[[ empty table ]] }')
if level == 0 then flush() end
else
local prefix = string.rep(' ', level + 1)
puts('{')
flush()
for k, v in pairs(input) do
if type(k) == 'string' then
puts('%s[%q] = ', prefix, k)
else
puts('%s[%s] = ', prefix, k)
end
printValue(v, level + 1)
puts(',')
flush()
end
puts('%s}', string.rep(' ', level))
if level == 0 then flush() end
end
end
function printValue(input, level)
level = level or 0
local t = type(input)
if t == 'nil' then
puts('%s', 'nil')
if level == 0 then flush() end
elseif t == 'number' then
puts('%f', input)
if level == 0 then flush() end
elseif t == 'string' then
puts('%q', tostring(input))
if level == 0 then flush() end
elseif t == 'boolean' then
puts('%s', tostring(input))
if level == 0 then flush() end
elseif t == 'table' then
printTable(input, level)
else
puts('unknown value type %q', t)
if level == 0 then flush() end
end
end
end

View File

@ -99,7 +99,7 @@ local function entityTypeNamesToIntegers(input, fullName)
error('invalid entity type ' .. tostring(v) .. ' for ' .. fullName .. ' in types table at index ' .. i, 3) error('invalid entity type ' .. tostring(v) .. ' for ' .. fullName .. ' in types table at index ' .. i, 3)
end end
entityTypes[i] = lookup input[i] = lookup
end end
return input return input

View File

@ -12,12 +12,22 @@ object LuaTests {
fun test() { fun test() {
val lua = LuaThread() val lua = LuaThread()
lua.ensureExtraCapacity(1000) lua.push {
throw IllegalArgumentException("This is error message")
}
lua.loadGlobal("collectgarbage") lua.storeGlobal("test")
lua.push("count")
lua.call(1, 1) lua.call {
println(lua.popDouble()!! * 1024) lua.load("""
local function errornous()
test()
end
local cor = coroutine.create(errornous)
print(coroutine.resume(cor))
""".trimIndent())
}
lua.close() lua.close()
} }