Compare commits
5 Commits
49d6cb0d89
...
f9b339c0e4
Author | SHA1 | Date | |
---|---|---|---|
f9b339c0e4 | |||
d46ffdb66b | |||
88eb691045 | |||
8e7f6ee5c3 | |||
5c0316746e |
@ -13,12 +13,7 @@ static int lua_jniFunc(lua_State *state) {
|
||||
jint result = (*env)->CallIntMethod(env, *lua_JCClosure, callback, (long long) state);
|
||||
|
||||
if (result <= -1) {
|
||||
const char* errMsg = lua_tostring(state, -1);
|
||||
|
||||
if (errMsg == NULL)
|
||||
return luaL_error(state, "Internal JVM Error");
|
||||
|
||||
return luaL_error(state, "%s", errMsg);
|
||||
return lua_error(state);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -32,9 +32,9 @@ public interface LuaJNR {
|
||||
@IgnoreError
|
||||
public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback);
|
||||
@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);
|
||||
@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.
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
/**
|
||||
@ -16,4 +17,5 @@ data class BehaviorNodeDefinition(
|
||||
*/
|
||||
val properties: ImmutableMap<String, NodeParameter> = ImmutableMap.of(),
|
||||
val output: ImmutableMap<String, NodeOutput> = ImmutableMap.of(),
|
||||
val script: AssetPath? = null,
|
||||
)
|
||||
|
@ -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) {
|
||||
fun push(lua: LuaThread) {
|
||||
lua.pushTable(hashSize = 3)
|
||||
lua.setTableValue("type", type.ordinal)
|
||||
lua.setTableValue("type", type.ordinal + 1)
|
||||
|
||||
if (key != null)
|
||||
lua.setTableValue("key", key)
|
||||
|
@ -20,7 +20,7 @@ data class NodeParameter(val type: NodeParameterType, val value: NodeParameterVa
|
||||
|
||||
fun push(lua: LuaThread) {
|
||||
lua.pushTable(hashSize = 3)
|
||||
lua.setTableValue("type", type.ordinal)
|
||||
lua.setTableValue("type", type.ordinal + 1)
|
||||
|
||||
if (value.key != null) {
|
||||
lua.setTableValue("key", value.key)
|
||||
|
@ -274,7 +274,6 @@ data class ItemDescriptor(
|
||||
push(parameters)
|
||||
push(level)
|
||||
push(seed)
|
||||
5
|
||||
}, { getJson() as JsonObject to getJson() as JsonObject }).get()
|
||||
} finally {
|
||||
lua.close()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.json
|
||||
|
||||
import com.github.luben.zstd.ZstdDictDecompress
|
||||
import com.github.luben.zstd.ZstdInputStreamNoFinalizer
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
@ -36,7 +37,7 @@ private enum class InflateType {
|
||||
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)
|
||||
|
||||
when (inflate) {
|
||||
@ -53,12 +54,17 @@ private fun <T> ByteArray.callRead(inflate: InflateType, callable: DataInputStre
|
||||
}
|
||||
|
||||
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 {
|
||||
return callable(data)
|
||||
} 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.readJsonArrayInflated(): JsonArray = callRead(InflateType.ZLIB) { readJsonArray() }
|
||||
|
||||
fun ByteArray.readJsonElementZstd(): JsonElement = callRead(InflateType.ZSTD) { readJsonElement() }
|
||||
fun ByteArray.readJsonObjectZstd(): JsonObject = callRead(InflateType.ZSTD) { readJsonObject() }
|
||||
fun ByteArray.readJsonArrayZstd(): JsonArray = callRead(InflateType.ZSTD) { readJsonArray() }
|
||||
fun ByteArray.readJsonElementZstd(dictionary: ZstdDictDecompress? = null): JsonElement = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonElement() }
|
||||
fun ByteArray.readJsonObjectZstd(dictionary: ZstdDictDecompress? = null): JsonObject = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonObject() }
|
||||
fun ByteArray.readJsonArrayZstd(dictionary: ZstdDictDecompress? = null): JsonArray = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonArray() }
|
||||
|
||||
/**
|
||||
* Позволяет читать двоичный JSON прямиком в [JsonElement]
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.json
|
||||
|
||||
import com.github.luben.zstd.ZstdDictCompress
|
||||
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
@ -21,7 +22,7 @@ private enum class DeflateType {
|
||||
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()
|
||||
|
||||
when (deflate) {
|
||||
@ -38,6 +39,9 @@ private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6,
|
||||
val s = ZstdOutputStreamNoFinalizer(stream)
|
||||
s.setLevel(zstdCompressionLevel)
|
||||
|
||||
if (zstdDictionary != null)
|
||||
s.setDict(zstdDictionary)
|
||||
|
||||
DataOutputStream(BufferedOutputStream(s, 0x10000)).use {
|
||||
callable(it, this)
|
||||
}
|
||||
@ -57,9 +61,9 @@ fun JsonElement.writeJsonElementDeflated(): ByteArray = callWrite(DeflateType.ZL
|
||||
fun JsonObject.writeJsonObjectDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonObject(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 JsonObject.writeJsonObjectZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonObject(it) }
|
||||
fun JsonArray.writeJsonArrayZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonArray(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, dictionary: ZstdDictCompress? = null): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level, zstdDictionary = dictionary) { writeJsonObject(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) {
|
||||
when (value) {
|
||||
|
@ -1,6 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
data class CommonHandleRegistry(
|
||||
val future: LuaHandle,
|
||||
val pathFinder: LuaHandle,
|
||||
)
|
||||
val future: LuaHandle = LuaHandle.Nil,
|
||||
val pathFinder: LuaHandle = LuaHandle.Nil,
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = CommonHandleRegistry()
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import ru.dbotthepony.kstarbound.math.Line2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2f
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.util.floorToInt
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
|
||||
// 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> {
|
||||
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)
|
||||
?: 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>? {
|
||||
@ -223,18 +224,20 @@ fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? {
|
||||
push(1)
|
||||
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()
|
||||
x ?: return null
|
||||
|
||||
push(2)
|
||||
loadTableValue(abs)
|
||||
|
||||
val y = getLong()
|
||||
val y = getDouble()
|
||||
pop()
|
||||
y ?: return null
|
||||
|
||||
return Vector2i(x.toInt(), y.toInt())
|
||||
return Vector2i(x.floorToInt(), y.floorToInt())
|
||||
}
|
||||
|
||||
fun LuaThread.ArgStack.nextVector2i(position: Int = this.position++): Vector2i {
|
||||
@ -262,31 +265,31 @@ fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? {
|
||||
push(1)
|
||||
loadTableValue(abs)
|
||||
|
||||
val x = getLong()
|
||||
val x = getFloat()
|
||||
pop()
|
||||
x ?: return null
|
||||
|
||||
push(2)
|
||||
loadTableValue(abs)
|
||||
|
||||
val y = getLong()
|
||||
val y = getFloat()
|
||||
pop()
|
||||
y ?: return null
|
||||
|
||||
push(3)
|
||||
loadTableValue(abs)
|
||||
|
||||
val z = getLong()
|
||||
val z = getFloat()
|
||||
pop()
|
||||
z ?: return null
|
||||
|
||||
push(4)
|
||||
loadTableValue(abs)
|
||||
|
||||
val w = getLong() ?: 255L
|
||||
val w = getFloat() ?: 255f
|
||||
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 {
|
||||
@ -365,32 +368,33 @@ fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? {
|
||||
push(1)
|
||||
loadTableValue(abs)
|
||||
|
||||
val x = getLong()
|
||||
// FIXME: original engine parity
|
||||
val x = getDouble()
|
||||
pop()
|
||||
x ?: return null
|
||||
|
||||
push(2)
|
||||
loadTableValue(abs)
|
||||
|
||||
val y = getLong()
|
||||
val y = getDouble()
|
||||
pop()
|
||||
y ?: return null
|
||||
|
||||
push(3)
|
||||
loadTableValue(abs)
|
||||
|
||||
val z = getLong()
|
||||
val z = getDouble()
|
||||
pop()
|
||||
z ?: return null
|
||||
|
||||
push(4)
|
||||
loadTableValue(abs)
|
||||
|
||||
val w = getLong()
|
||||
val w = getDouble()
|
||||
pop()
|
||||
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 {
|
||||
|
@ -16,17 +16,6 @@ const val LUA_ERRMEM = 4
|
||||
const val LUA_ERRERR = 5
|
||||
|
||||
class InvalidLuaSyntaxException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
class LuaMemoryAllocException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
class LuaGCException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
class LuaMemoryAllocException(message: String? = null, cause: Throwable? = null) : Error(message, cause)
|
||||
class LuaException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
class LuaRuntimeException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
class LuaRuntimeException(message: String? = null, cause: Throwable? = null, writeStackTrace: Boolean = true) : RuntimeException(message, cause, true, writeStackTrace)
|
||||
|
@ -1,36 +1,169 @@
|
||||
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.json.InternedJsonElementAdapter
|
||||
import java.io.Closeable
|
||||
import java.lang.ref.Cleaner.Cleanable
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class LuaHandle(private val parent: LuaSharedState, val handle: Int, val key: Any?) : Closeable {
|
||||
private val cleanable: Cleanable
|
||||
interface LuaHandle : Closeable {
|
||||
fun push(into: LuaThread)
|
||||
override fun close() {}
|
||||
fun toJson(): JsonElement
|
||||
val type: LuaType
|
||||
|
||||
var isValid = true
|
||||
private set
|
||||
|
||||
init {
|
||||
val parent = parent
|
||||
val handle = handle
|
||||
val key = key
|
||||
|
||||
cleanable = Starbound.CLEANER.register(this) {
|
||||
parent.freeHandle(handle, key)
|
||||
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
|
||||
}
|
||||
|
||||
fun push(into: LuaThread) {
|
||||
check(isValid) { "Tried to use NULL handle!" }
|
||||
parent.handlesThread.push()
|
||||
parent.handlesThread.copy(handle, -1)
|
||||
parent.handlesThread.moveStackValuesOnto(into)
|
||||
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
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!isValid) return
|
||||
cleanable.clean()
|
||||
isValid = false
|
||||
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
|
||||
|
||||
var isValid = true
|
||||
private set
|
||||
|
||||
override val type: LuaType by lazy(LazyThreadSafetyMode.NONE) {
|
||||
check(isValid) { "Tried to use NULL handle!" }
|
||||
parent.handlesThread.typeAt(handle)
|
||||
}
|
||||
|
||||
init {
|
||||
val parent = WeakReference(parent)
|
||||
val handle = handle
|
||||
val key = key
|
||||
|
||||
cleanable = Starbound.CLEANER.register(this) {
|
||||
parent.get()?.freeHandle(handle, key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun push(into: LuaThread) {
|
||||
check(isValid) { "Tried to use NULL handle!" }
|
||||
parent.handlesThread.push()
|
||||
parent.handlesThread.copy(handle, -1)
|
||||
parent.handlesThread.moveStackValuesOnto(into)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!isValid) return
|
||||
cleanable.clean()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,19 +38,16 @@ class LuaMessageHandlerComponent(val lua: LuaThread, val nameProvider: () -> Str
|
||||
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 top = lua.stackTop
|
||||
|
||||
try {
|
||||
lua.push(handler)
|
||||
lua.push(isLocal)
|
||||
val amountOfArguments = arguments(lua)
|
||||
check(amountOfArguments >= 0) { "Invalid amount of arguments to pass to Lua handler: $amountOfArguments" }
|
||||
|
||||
lua.call(amountOfArguments + 1, 1)
|
||||
|
||||
return lua.getJson()
|
||||
return lua.call(1, {
|
||||
push(handler)
|
||||
push(isLocal)
|
||||
arguments(lua)
|
||||
}, { getJson(it) })
|
||||
} catch (err: Throwable) {
|
||||
if (logPacer.consumeAndReturnDeadline() <= 0L)
|
||||
LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err)
|
||||
|
@ -30,11 +30,24 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
||||
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() {
|
||||
if (!isValid) return
|
||||
isValid = false
|
||||
namedHandles.clear()
|
||||
cleanable.clean()
|
||||
errorToStringFunction = LuaHandle.Nil
|
||||
errorTrapFunction = LuaHandle.Nil
|
||||
commonHandles = CommonHandleRegistry.EMPTY
|
||||
}
|
||||
|
||||
fun initializeHandles(mainThread: LuaThread) {
|
||||
@ -45,6 +58,36 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
||||
future = future,
|
||||
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?) {
|
||||
@ -79,10 +122,10 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
||||
|
||||
if (freeHandles.isEmpty()) {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
@ -92,7 +135,7 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
||||
handlesThread.copy(-1, handle)
|
||||
handlesThread.pop()
|
||||
|
||||
return LuaHandle(this, handle, name).also {
|
||||
return LuaHandle.Regular(this, handle, name).also {
|
||||
if (name != null) namedHandles[name] = it
|
||||
}
|
||||
}
|
||||
|
@ -90,14 +90,19 @@ class LuaThread private constructor(
|
||||
this.storeGlobal("math")
|
||||
LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
|
||||
this.storeGlobal("utf8")
|
||||
LuaJNR.INSTANCE.luaopen_debug(this.pointer)
|
||||
this.storeGlobal("debug")
|
||||
LuaJNR.INSTANCE.luaopen_os(this.pointer)
|
||||
this.storeGlobal("os")
|
||||
|
||||
sharedState.initializeHandles(this)
|
||||
|
||||
provideUtilityBindings(this)
|
||||
provideRootBindings(this)
|
||||
|
||||
load(globalScript, "@/internal/global.lua")
|
||||
call()
|
||||
call {
|
||||
load(globalScript, "@/internal/global.lua")
|
||||
}
|
||||
}
|
||||
|
||||
fun interface Fn {
|
||||
@ -204,115 +209,219 @@ class LuaThread private constructor(
|
||||
closure.dispose()
|
||||
}
|
||||
|
||||
fun call(numArgs: Int = 0, numResults: Int = 0): Int {
|
||||
sharedState.cleanup()
|
||||
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
|
||||
private fun pcall(numArgs: Int, numResults: Int, handler: Int) {
|
||||
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, handler, 0L, 0L)
|
||||
|
||||
if (status == LUA_ERRRUN) {
|
||||
throw LuaRuntimeException(this.getString())
|
||||
}
|
||||
val errObject = typeAt(-1)
|
||||
|
||||
return status
|
||||
if (errObject == LuaType.STRING)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns boolean indicating whenever function exists
|
||||
*/
|
||||
inline fun invokeGlobal(name: String, arguments: LuaThread.() -> Int): Boolean {
|
||||
fun callConditional(block: LuaThread.() -> Boolean) {
|
||||
sharedState.cleanup()
|
||||
|
||||
val top = stackTop
|
||||
push(sharedState.errorTrapFunction)
|
||||
|
||||
try {
|
||||
val type = loadGlobal(name)
|
||||
if (type != LuaType.FUNCTION)
|
||||
return false
|
||||
if (block(this)) {
|
||||
val newTop = stackTop
|
||||
|
||||
val numArguments = arguments(this)
|
||||
check(numArguments >= 0) { "Invalid amount of arguments provided to Lua function" }
|
||||
call(numArguments)
|
||||
return true
|
||||
if (newTop == top + 1) {
|
||||
throw IllegalArgumentException("No function was pushed to stack")
|
||||
}
|
||||
|
||||
pcall(newTop - top - 2, 0, top + 1)
|
||||
}
|
||||
} finally {
|
||||
setTop(top)
|
||||
LuaJNR.INSTANCE.lua_settop(pointer, top)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns boolean indicating whenever function exists
|
||||
*/
|
||||
fun invokeGlobal(name: String, numArguments: Int): Boolean {
|
||||
fun call(block: LuaThread.() -> Unit) {
|
||||
return callConditional { block(this); true }
|
||||
}
|
||||
|
||||
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
|
||||
push(sharedState.errorTrapFunction)
|
||||
|
||||
try {
|
||||
val type = loadGlobal(name)
|
||||
if (block(this)) {
|
||||
val newTop = stackTop
|
||||
|
||||
if (type != LuaType.FUNCTION) {
|
||||
pop(numArguments)
|
||||
return false
|
||||
if (newTop == top + 1) {
|
||||
throw IllegalArgumentException("No function was pushed to stack")
|
||||
}
|
||||
|
||||
pcall(newTop - top - 2, numResults, top + 1)
|
||||
return KOptional(resultHandler(this, top + 2))
|
||||
} else {
|
||||
return KOptional()
|
||||
}
|
||||
} finally {
|
||||
LuaJNR.INSTANCE.lua_settop(pointer, top)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> call(numResults: Int, block: LuaThread.() -> Unit, resultHandler: LuaThread.(firstStackIndex: Int) -> T): T {
|
||||
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
|
||||
push(sharedState.errorTrapFunction)
|
||||
|
||||
try {
|
||||
if (!block(this))
|
||||
return null
|
||||
|
||||
val newTop = stackTop
|
||||
|
||||
if (newTop == top + 1) {
|
||||
throw IllegalArgumentException("No function was pushed to stack")
|
||||
}
|
||||
|
||||
call(numArguments)
|
||||
return true
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (i != numResults) {
|
||||
push()
|
||||
copy(stackIndex, -1)
|
||||
}
|
||||
|
||||
moveStackValuesOnto(sharedState.handlesThread)
|
||||
handles.add(sharedState.allocateHandle(null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handles
|
||||
} finally {
|
||||
setTop(top)
|
||||
LuaJNR.INSTANCE.lua_settop(pointer, 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
|
||||
fun call(numResults: Int, block: LuaThread.() -> Unit): List<LuaHandle> {
|
||||
return callConditional(numResults, { block(this); true })!!
|
||||
}
|
||||
|
||||
try {
|
||||
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 KOptional()
|
||||
return@callConditional false
|
||||
|
||||
val numArguments = arguments(this)
|
||||
call(numArguments, numResults)
|
||||
return KOptional(results(this, top + 1))
|
||||
} finally {
|
||||
setTop(top)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> eval(chunk: String, name: String = "eval", numResults: Int, arguments: LuaThread.() -> Int, results: LuaThread.(firstValue: Int) -> T): T {
|
||||
require(numResults > 0) { "Invalid amount of results: $numResults" }
|
||||
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
|
||||
|
||||
val top = stackTop
|
||||
arguments(this)
|
||||
return@callConditional true
|
||||
}, resultHandler)
|
||||
}
|
||||
|
||||
try {
|
||||
val numArguments = arguments(this)
|
||||
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)
|
||||
call(numArguments, numResults)
|
||||
return results(this, top + 1)
|
||||
} finally {
|
||||
setTop(top)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Int) {
|
||||
val top = stackTop
|
||||
|
||||
try {
|
||||
val numArguments = arguments(this)
|
||||
fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Unit) {
|
||||
return call {
|
||||
load(chunk, name)
|
||||
call(numArguments, 0)
|
||||
} finally {
|
||||
setTop(top)
|
||||
arguments(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun eval(chunk: String, name: String = "eval"): JsonElement {
|
||||
val top = stackTop
|
||||
|
||||
try {
|
||||
return call(1, {
|
||||
load(chunk, name)
|
||||
call(numResults = 1)
|
||||
return getJson() ?: JsonNull.INSTANCE
|
||||
} finally {
|
||||
setTop(top)
|
||||
}
|
||||
}, { getJson(it)!! })
|
||||
}
|
||||
|
||||
private val attachedScripts = ArrayList<String>()
|
||||
@ -333,8 +442,9 @@ class LuaThread private constructor(
|
||||
try {
|
||||
// minor hiccups during unpopulated script cache should be tolerable
|
||||
for ((chunk, path) in loadScripts) {
|
||||
load(chunk.join(), "@$path")
|
||||
call()
|
||||
call {
|
||||
load(chunk.join(), "@$path")
|
||||
}
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
LOGGER.error("Failed to attach scripts to Lua environment", err)
|
||||
@ -343,16 +453,18 @@ class LuaThread private constructor(
|
||||
|
||||
try {
|
||||
if (callInit) {
|
||||
val type = loadGlobal("init")
|
||||
callConditional {
|
||||
val type = loadGlobal("init")
|
||||
|
||||
if (type == LuaType.FUNCTION) {
|
||||
call()
|
||||
} else if (type == LuaType.NIL || type == LuaType.NONE) {
|
||||
pop()
|
||||
} else {
|
||||
pop()
|
||||
throw LuaRuntimeException("init is not a function: $type")
|
||||
if (type == LuaType.NIL || type == LuaType.NONE) {
|
||||
return@callConditional false
|
||||
} else if (type != LuaType.FUNCTION) {
|
||||
throw LuaRuntimeException("init is not a function: $type")
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
LOGGER.error("Failed to call init() in Lua environment", err)
|
||||
@ -371,8 +483,9 @@ class LuaThread private constructor(
|
||||
sharedState.ensureValid()
|
||||
if (initCalled) {
|
||||
// minor hiccups during unpopulated script cache should be tolerable
|
||||
load(Starbound.readLuaScript(script).join(), "@$script")
|
||||
call()
|
||||
call {
|
||||
load(Starbound.readLuaScript(script).join(), "@$script")
|
||||
}
|
||||
} else {
|
||||
attachedScripts.add(script)
|
||||
}
|
||||
@ -710,8 +823,8 @@ class LuaThread private constructor(
|
||||
|
||||
try {
|
||||
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
||||
keyVisitor(this, abs + 1)
|
||||
valueVisitor(this, abs + 2)
|
||||
keyVisitor(this, top)
|
||||
valueVisitor(this, top + 1)
|
||||
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
||||
}
|
||||
} finally {
|
||||
@ -734,7 +847,7 @@ class LuaThread private constructor(
|
||||
|
||||
try {
|
||||
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)
|
||||
}
|
||||
} finally {
|
||||
@ -757,7 +870,7 @@ class LuaThread private constructor(
|
||||
|
||||
try {
|
||||
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)
|
||||
}
|
||||
} finally {
|
||||
@ -780,7 +893,7 @@ class LuaThread private constructor(
|
||||
|
||||
try {
|
||||
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)
|
||||
}
|
||||
} 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()
|
||||
val realLuaState: LuaThread
|
||||
|
||||
@ -1118,69 +1231,33 @@ class LuaThread private constructor(
|
||||
}
|
||||
|
||||
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 {
|
||||
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" }
|
||||
return value
|
||||
} catch (err: Throwable) {
|
||||
try {
|
||||
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
|
||||
}
|
||||
realLuaState.push(err)
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
fun push(function: Fn, performanceCritical: Boolean) {
|
||||
fun push(err: Throwable) {
|
||||
pushTable()
|
||||
push("__tostring")
|
||||
push(sharedState.errorToStringFunction)
|
||||
setTableValue()
|
||||
pushObject(err)
|
||||
}
|
||||
|
||||
fun push(function: Fn) {
|
||||
sharedState.ensureValid()
|
||||
|
||||
LuaJNI.lua_pushcclosure(pointer.address()) {
|
||||
closure(it, function, performanceCritical)
|
||||
closure(it, function)
|
||||
}
|
||||
}
|
||||
|
||||
fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES)
|
||||
|
||||
fun <T> push(self: T, function: Binding<T>) {
|
||||
push {
|
||||
function.invoke(self, it)
|
||||
@ -1363,6 +1440,17 @@ class LuaThread private constructor(
|
||||
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() {
|
||||
push()
|
||||
copy(-2, -1)
|
||||
@ -1590,8 +1678,8 @@ class LuaThread private constructor(
|
||||
}
|
||||
|
||||
is JsonArray -> {
|
||||
this.loadGlobal("jarray")
|
||||
this.call(numResults = 1)
|
||||
val handle = call(1) { loadGlobal("jarray") }.first()
|
||||
handle.push(this)
|
||||
val index = this.stackTop
|
||||
|
||||
for ((i, v) in value.withIndex()) {
|
||||
@ -1603,8 +1691,8 @@ class LuaThread private constructor(
|
||||
}
|
||||
|
||||
is JsonObject -> {
|
||||
this.loadGlobal("jobject")
|
||||
this.call(numResults = 1)
|
||||
val handle = call(1) { loadGlobal("jobject") }.first()
|
||||
handle.push(this)
|
||||
|
||||
val index = this.stackTop
|
||||
|
||||
@ -1622,6 +1710,10 @@ class LuaThread private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "LuaThread at ${pointer.address()}"
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOGGER = LogManager.getLogger()
|
||||
|
||||
|
@ -38,23 +38,26 @@ class LuaUpdateComponent(val lua: LuaThread, val name: Any) {
|
||||
if (steps >= stepCount) {
|
||||
steps %= stepCount
|
||||
|
||||
val type = lua.loadGlobal("update")
|
||||
lua.callConditional {
|
||||
val type = loadGlobal("update")
|
||||
|
||||
if (type != lastType) {
|
||||
lastType = type
|
||||
/*if (type != lastType) {
|
||||
lastType = type
|
||||
|
||||
if (type != LuaType.FUNCTION) {
|
||||
LOGGER.warn("Lua environment for $name has $type as global 'update', script update wasn't called")
|
||||
if (type != LuaType.FUNCTION) {
|
||||
LOGGER.warn("Lua environment for $name has $type as global 'update', script update wasn't called")
|
||||
}
|
||||
}*/
|
||||
|
||||
if (type == LuaType.FUNCTION) {
|
||||
preRun()
|
||||
push(stepCount * Starbound.TIMESTEP)
|
||||
return@callConditional true
|
||||
} else {
|
||||
return@callConditional false
|
||||
}
|
||||
}
|
||||
|
||||
if (type == LuaType.FUNCTION) {
|
||||
preRun()
|
||||
lua.push(stepCount * Starbound.TIMESTEP)
|
||||
lua.call(1)
|
||||
} else {
|
||||
lua.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,12 +100,12 @@ private fun setDropPool(self: MonsterEntity, args: LuaThread.ArgStack): Int {
|
||||
|
||||
private fun toAbsolutePosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
|
||||
args.lua.push(self.movement.getAbsolutePosition(args.nextVector2d()))
|
||||
return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun mouthPosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
|
||||
args.lua.push(self.mouthPosition)
|
||||
return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
// This callback is registered here rather than in
|
||||
|
@ -386,6 +386,7 @@ fun provideServerWorldBindings(self: ServerWorld, lua: LuaThread) {
|
||||
lua.setTableValueToStub("setLayerEnvironmentBiome")
|
||||
lua.setTableValueToStub("setPlanetType")
|
||||
|
||||
lua.load(script, "@/internal/server_world.lua")
|
||||
lua.call()
|
||||
lua.call {
|
||||
load(script, "@/internal/server_world.lua")
|
||||
}
|
||||
}
|
||||
|
@ -160,91 +160,89 @@ private fun printJson(args: LuaThread.ArgStack): Int {
|
||||
}
|
||||
|
||||
fun provideUtilityBindings(lua: LuaThread) {
|
||||
with(lua) {
|
||||
push {
|
||||
LuaThread.LOGGER.info(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
storeGlobal("__print")
|
||||
|
||||
push {
|
||||
LuaThread.LOGGER.warn(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
storeGlobal("__print_warn")
|
||||
|
||||
push {
|
||||
LuaThread.LOGGER.error(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
storeGlobal("__print_error")
|
||||
|
||||
push {
|
||||
LuaThread.LOGGER.fatal(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
storeGlobal("__print_fatal")
|
||||
|
||||
push {
|
||||
val path = it.nextString()
|
||||
|
||||
try {
|
||||
load(Starbound.readLuaScript(path).join(), "@$path")
|
||||
1
|
||||
} catch (err: Exception) {
|
||||
LuaThread.LOGGER.error("Exception loading Lua script $path", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
storeGlobal("__require")
|
||||
|
||||
push {
|
||||
push(random.nextDouble())
|
||||
1
|
||||
}
|
||||
|
||||
storeGlobal("__random_double")
|
||||
|
||||
push {
|
||||
push(random.nextLong(it.nextLong(), it.nextLong()))
|
||||
1
|
||||
}
|
||||
|
||||
storeGlobal("__random_long")
|
||||
|
||||
push {
|
||||
random = random(it.nextLong())
|
||||
0
|
||||
}
|
||||
|
||||
storeGlobal("__random_seed")
|
||||
|
||||
push {
|
||||
push(it.lua.getNamedHandle(it.nextString()))
|
||||
1
|
||||
}
|
||||
|
||||
storeGlobal("gethandle")
|
||||
|
||||
push {
|
||||
val find = it.lua.findNamedHandle(it.nextString())
|
||||
|
||||
if (find == null) {
|
||||
0
|
||||
} else {
|
||||
push(find)
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
storeGlobal("findhandle")
|
||||
lua.push {
|
||||
LuaThread.LOGGER.info(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
lua.storeGlobal("__print")
|
||||
|
||||
lua.push {
|
||||
LuaThread.LOGGER.warn(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
lua.storeGlobal("__print_warn")
|
||||
|
||||
lua.push {
|
||||
LuaThread.LOGGER.error(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
lua.storeGlobal("__print_error")
|
||||
|
||||
lua.push {
|
||||
LuaThread.LOGGER.fatal(it.nextString())
|
||||
0
|
||||
}
|
||||
|
||||
lua.storeGlobal("__print_fatal")
|
||||
|
||||
lua.push {
|
||||
val path = it.nextString()
|
||||
|
||||
try {
|
||||
it.lua.load(Starbound.readLuaScript(path).join(), "@$path")
|
||||
1
|
||||
} catch (err: Exception) {
|
||||
LuaThread.LOGGER.error("Exception loading Lua script $path", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
lua.storeGlobal("__require")
|
||||
|
||||
lua.push {
|
||||
it.lua.push(it.lua.random.nextDouble())
|
||||
1
|
||||
}
|
||||
|
||||
lua.storeGlobal("__random_double")
|
||||
|
||||
lua.push {
|
||||
it.lua.push(it.lua.random.nextLong(it.nextLong(), it.nextLong()))
|
||||
1
|
||||
}
|
||||
|
||||
lua.storeGlobal("__random_long")
|
||||
|
||||
lua.push {
|
||||
it.lua.random = random(it.nextLong())
|
||||
0
|
||||
}
|
||||
|
||||
lua.storeGlobal("__random_seed")
|
||||
|
||||
lua.push {
|
||||
it.lua.push(it.lua.getNamedHandle(it.nextString()))
|
||||
1
|
||||
}
|
||||
|
||||
lua.storeGlobal("gethandle")
|
||||
|
||||
lua.push {
|
||||
val find = it.lua.findNamedHandle(it.nextString())
|
||||
|
||||
if (find == null) {
|
||||
0
|
||||
} else {
|
||||
it.lua.push(find)
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
lua.storeGlobal("findhandle")
|
||||
|
||||
lua.pushTable()
|
||||
lua.dup()
|
||||
lua.storeGlobal("sb")
|
||||
|
@ -943,6 +943,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaThread) {
|
||||
|
||||
lua.pop()
|
||||
|
||||
lua.load(worldScript, "@/internal/world.lua")
|
||||
lua.call()
|
||||
lua.call {
|
||||
load(worldScript, "@/internal/world.lua")
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
@ -96,7 +97,17 @@ private data class CallScriptData(
|
||||
|
||||
private fun LuaThread.ArgStack.getScriptData(): CallScriptData? {
|
||||
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 {
|
||||
skip(3)
|
||||
return null
|
||||
|
@ -29,7 +29,7 @@ private fun name(self: WorldObject, args: LuaThread.ArgStack): Int {
|
||||
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)
|
||||
return 1
|
||||
}
|
||||
@ -320,7 +320,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaThread) {
|
||||
lua.storeGlobal("object")
|
||||
|
||||
lua.pushBinding(self, "name", ::name)
|
||||
lua.pushBinding(self, "directions", ::directions)
|
||||
lua.pushBinding(self, "direction", ::direction)
|
||||
lua.pushBinding(self, "position", ::position)
|
||||
lua.pushBinding(self, "setInteractive", ::setInteractive)
|
||||
lua.pushBinding(self, "uniqueId", ::uniqueId)
|
||||
|
@ -29,7 +29,7 @@ private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Ma
|
||||
if (parameter.key != null)
|
||||
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)
|
||||
str = parameter.value.asString
|
||||
|
||||
@ -144,7 +144,12 @@ private fun createNode(
|
||||
functions.add(name)
|
||||
|
||||
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) {
|
||||
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" }
|
||||
lua.push(name)
|
||||
push(lua, parameters)
|
||||
push(lua, output)
|
||||
lua.call(3, 1)
|
||||
val handle = lua.createHandle()
|
||||
val handle = lua.call(1) {
|
||||
check(loadGlobal("ActionNode") == LuaType.FUNCTION) { "Global ActionNode is not a Lua function" }
|
||||
push(name)
|
||||
push(this, parameters)
|
||||
push(this, output)
|
||||
}[0]
|
||||
|
||||
handles.add(handle)
|
||||
lua.pop()
|
||||
return handle
|
||||
}
|
||||
|
||||
@ -169,15 +174,14 @@ private fun createNode(
|
||||
functions.add(name)
|
||||
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" }
|
||||
lua.push(name)
|
||||
push(lua, parameters)
|
||||
lua.push(sacrifice)
|
||||
lua.call(3, 1)
|
||||
val handle = lua.createHandle()
|
||||
handles.add(handle)
|
||||
lua.pop()
|
||||
val handle = lua.call(1) {
|
||||
check(loadGlobal("DecoratorNode") == LuaType.FUNCTION) { "Global DecoratorNode is not a Lua function" }
|
||||
push(name)
|
||||
push(this, parameters)
|
||||
push(sacrifice)
|
||||
}[0]
|
||||
|
||||
handles.add(handle)
|
||||
return handle
|
||||
}
|
||||
|
||||
@ -189,21 +193,20 @@ private fun createNode(
|
||||
|
||||
val factory = CompositeNodeType.entries.valueOf(name)
|
||||
|
||||
check(lua.loadGlobal(factory.fnName) == LuaType.FUNCTION) { "Global ${factory.fnName} is not a Lua function" }
|
||||
push(lua, parameters)
|
||||
val handle = lua.call(1) {
|
||||
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()) {
|
||||
lua.push(i + 1L)
|
||||
lua.push(child)
|
||||
lua.setTableValue()
|
||||
}
|
||||
for ((i, child) in children.withIndex()) {
|
||||
push(i + 1L)
|
||||
push(child)
|
||||
setTableValue()
|
||||
}
|
||||
}[0]
|
||||
|
||||
lua.call(2, 1)
|
||||
val handle = lua.createHandle()
|
||||
handles.add(handle)
|
||||
lua.pop()
|
||||
return handle
|
||||
}
|
||||
|
||||
@ -212,11 +215,7 @@ private fun createNode(
|
||||
}
|
||||
|
||||
private fun createBlackboard(lua: LuaThread): LuaHandle {
|
||||
lua.loadGlobal("Blackboard")
|
||||
lua.call(numResults = 1)
|
||||
val handle = lua.createHandle()
|
||||
lua.pop()
|
||||
return handle
|
||||
return lua.call(1) { lua.loadGlobal("Blackboard") }[0]
|
||||
}
|
||||
|
||||
private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
|
||||
@ -269,19 +268,36 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
|
||||
}
|
||||
|
||||
handles.add(blackboard)
|
||||
|
||||
args.lua.ensureExtraCapacity(40)
|
||||
|
||||
mergedParams.scripts.forEach { scripts.add(it.fullPath) }
|
||||
val root = createNode(args.lua, mergedParams.root, mergedParams.mappedParameters, blackboard, scripts, functions, handles)
|
||||
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)
|
||||
args.lua.push(root)
|
||||
val handle = args.lua.call(1) {
|
||||
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() }
|
||||
return 1
|
||||
}
|
||||
@ -297,6 +313,7 @@ fun provideBehaviorBindings(lua: LuaThread) {
|
||||
|
||||
lua.pop()
|
||||
|
||||
lua.load(script, "@/internal/behavior.lua")
|
||||
lua.call()
|
||||
lua.call {
|
||||
load(script, "@/internal/behavior.lua")
|
||||
}
|
||||
}
|
||||
|
@ -97,10 +97,12 @@ class LegacyWireProcessor(val world: ServerWorld) {
|
||||
if (newState != node.state) {
|
||||
try {
|
||||
node.state = newState
|
||||
entity.lua.pushTable(hashSize = 2)
|
||||
entity.lua.setTableValue("node", i)
|
||||
entity.lua.setTableValue("level", newState)
|
||||
entity.lua.invokeGlobal("onInputNodeChange", 1)
|
||||
|
||||
entity.lua.invokeGlobal("onInputNodeChange") {
|
||||
pushTable(hashSize = 2)
|
||||
setTableValue("node", i)
|
||||
setTableValue("level", newState)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err)
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ import ru.dbotthepony.kstarbound.world.api.MutableTileState
|
||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.NPCEntity
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -232,7 +233,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
|
||||
for (obj in world.storage.loadEntities(pos).await()) {
|
||||
try {
|
||||
obj.joinWorld(world)
|
||||
if (obj !is NPCEntity)
|
||||
obj.joinWorld(world)
|
||||
} catch (err: Exception) {
|
||||
LOGGER.error("Exception while spawning entity $obj in world", err)
|
||||
}
|
||||
|
@ -1,9 +1,17 @@
|
||||
package ru.dbotthepony.kstarbound.server.world
|
||||
|
||||
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.JsonObject
|
||||
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.ObjectOpenHashSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -12,6 +20,7 @@ import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import kotlinx.coroutines.future.await
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.gson.JsonArrayCollector
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
@ -48,10 +57,12 @@ import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.lang.ref.Cleaner.Cleanable
|
||||
import java.nio.ByteBuffer
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -112,9 +123,17 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
`z` INTEGER NOT NULL,
|
||||
`parameters` BLOB NOT NULL,
|
||||
`planets` BLOB NOT NULL,
|
||||
`dictionary` INTEGER NOT NULL,
|
||||
PRIMARY KEY(`x`, `y`, `z`)
|
||||
)
|
||||
""".trimIndent())
|
||||
|
||||
it.execute("""
|
||||
CREATE TABLE IF NOT EXISTS `dictionary` (
|
||||
`version` INTEGER NOT NULL PRIMARY KEY,
|
||||
`data` BLOB NOT NULL
|
||||
)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
database.autoCommit = false
|
||||
@ -125,10 +144,13 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
private val scope = CoroutineScope(ScheduledCoroutineExecutor(carrier) + SupervisorJob())
|
||||
|
||||
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 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) {
|
||||
fun write(statement: PreparedStatement) {
|
||||
@ -164,19 +186,16 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
}.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) {
|
||||
statement.setInt(1, x)
|
||||
statement.setInt(2, y)
|
||||
statement.setInt(3, z)
|
||||
statement.setBytes(4, parameters)
|
||||
statement.setBytes(5, planets)
|
||||
statement.setInt(6, dictionary)
|
||||
|
||||
statement.execute()
|
||||
}
|
||||
@ -191,19 +210,16 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(): SerializedSystem {
|
||||
fun serialize(dict: Dicts? = null): SerializedSystem {
|
||||
return SerializedSystem(
|
||||
x, y, z,
|
||||
Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(8),
|
||||
Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(6, dictionary = dict?.compress),
|
||||
planets.entries.stream()
|
||||
.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
|
||||
@ -254,6 +270,239 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
.executor(Starbound.EXECUTOR)
|
||||
.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>? {
|
||||
selectSystem.setInt(1, pos.x)
|
||||
selectSystem.setInt(2, pos.y)
|
||||
@ -263,12 +512,21 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
if (it.next()) {
|
||||
val parametersBytes = it.getBytes(1)
|
||||
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
|
||||
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[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 region = chunkRegion(chunkPos)
|
||||
|
||||
val dict = dictionary
|
||||
|
||||
return CompletableFuture.supplyAsync(Supplier {
|
||||
val constellationCandidates = ArrayList<Vector2i>()
|
||||
val systemPositions = ArrayList<Vector3i>()
|
||||
@ -534,7 +794,7 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
|
||||
val system = generateSystem(random, pos) ?: continue
|
||||
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))
|
||||
|
||||
if (
|
||||
@ -554,6 +814,13 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
||||
serialized.write(insertChunk)
|
||||
systems.forEach { it.get().write(insertSystem) }
|
||||
database.commit()
|
||||
|
||||
if (latestDictionary == (dict?.version ?: 0)) {
|
||||
latestDictionaryCapacity += systems.size
|
||||
// enqueue dictionary maintenance
|
||||
carrier.execute(::dictionaryMaintenance)
|
||||
}
|
||||
|
||||
chunk
|
||||
}, carrier)
|
||||
}, 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))
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
||||
|
@ -122,18 +122,16 @@ abstract class SystemWorld(val location: Vector3i, val clock: JVMClock, val univ
|
||||
}
|
||||
|
||||
fun compatCoordinateSeed(coordinate: UniversePos, seedMix: String): Long {
|
||||
// original code is utterly broken here
|
||||
|
||||
// consider the following:
|
||||
// auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0;
|
||||
// auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0;
|
||||
|
||||
// first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0
|
||||
// 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
|
||||
// FIXME: original code is utterly broken here
|
||||
// consider the following:
|
||||
// auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0;
|
||||
// auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0;
|
||||
// first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0
|
||||
// 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
|
||||
|
||||
// TODO: Use correct logic when there are no legacy clients in this system
|
||||
// Correct logic properly randomizes starting planet orbits, and they feel much more natural
|
||||
// Correct logic properly randomizes starting planet orbits, and they feel much more natural
|
||||
|
||||
return staticRandom64(coordinate.location.x, coordinate.location.y, coordinate.location.z, if (coordinate.isPlanet) 1 else coordinate.planetOrbit, coordinate.satelliteOrbit, seedMix)
|
||||
}
|
||||
|
@ -333,13 +333,13 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
||||
|
||||
// TODO: hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit maybe?
|
||||
if (totalDamage > 0.0) {
|
||||
lua.pushTable(hashSize = 4)
|
||||
lua.setTableValue("sourceId", damage.request.sourceEntityId)
|
||||
lua.setTableValue("damage", totalDamage)
|
||||
lua.setTableValue("sourceDamage", damage.request.damage)
|
||||
lua.setTableValue("sourceKind", damage.request.damageSourceKind)
|
||||
|
||||
lua.invokeGlobal("damage", 1)
|
||||
lua.invokeGlobal("damage") {
|
||||
pushTable(hashSize = 4)
|
||||
setTableValue("sourceId", damage.request.sourceEntityId)
|
||||
setTableValue("damage", totalDamage)
|
||||
setTableValue("sourceDamage", damage.request.damage)
|
||||
setTableValue("sourceKind", damage.request.damageSourceKind)
|
||||
}
|
||||
}
|
||||
|
||||
if (health <= 0.0) {
|
||||
@ -350,7 +350,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -332,13 +332,13 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
|
||||
val totalDamage = notifications.sumOf { it.healthLost }
|
||||
|
||||
if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) {
|
||||
lua.pushTable(hashSize = 4)
|
||||
lua.setTableValue("sourceId", damage.request.sourceEntityId)
|
||||
lua.setTableValue("damage", totalDamage)
|
||||
lua.setTableValue("sourceDamage", damage.request.damage)
|
||||
lua.setTableValue("sourceKind", damage.request.damageSourceKind)
|
||||
|
||||
lua.invokeGlobal("damage", 1)
|
||||
lua.invokeGlobal("damage") {
|
||||
pushTable(hashSize = 4)
|
||||
setTableValue("sourceId", damage.request.sourceEntityId)
|
||||
setTableValue("damage", totalDamage)
|
||||
setTableValue("sourceDamage", damage.request.damage)
|
||||
setTableValue("sourceKind", damage.request.damageSourceKind)
|
||||
}
|
||||
}
|
||||
|
||||
return notifications
|
||||
@ -407,7 +407,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
|
||||
remove(RemovalReason.DYING)
|
||||
return
|
||||
} else {
|
||||
val shouldDie = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false)
|
||||
val shouldDie = lua.invokeGlobal("shouldDie", 1, {}, { getBoolean() == true }).orElse(false)
|
||||
|
||||
if (shouldDie) {
|
||||
remove(RemovalReason.DYING)
|
||||
@ -425,7 +425,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
|
||||
super.onRemove(world, reason)
|
||||
|
||||
if (isLocal)
|
||||
lua.invokeGlobal("die", 0)
|
||||
lua.invokeGlobal("die")
|
||||
|
||||
val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() }
|
||||
|
||||
|
@ -191,7 +191,6 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
|
||||
fun experienceDamage(damage: DamageData): List<DamageNotification> {
|
||||
val results = lua.invokeGlobal("applyDamageRequest", 1, {
|
||||
push(Starbound.gson.toJsonTree(damage))
|
||||
1
|
||||
}, { getJson() as? JsonArray }).flatMap { KOptional.ofNullable(it) }
|
||||
|
||||
if (results.isPresent) {
|
||||
@ -632,7 +631,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
|
||||
|
||||
fun remove() {
|
||||
if (entity.isLocal)
|
||||
lua.invokeGlobal("onExpire", 0)
|
||||
lua.invokeGlobal("onExpire")
|
||||
|
||||
uniqueEffectMetadata.remove(metadataNetworkID)
|
||||
|
||||
|
@ -306,13 +306,13 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
fun addConnection(connection: WireConnection) {
|
||||
if (connection !in connectionsInternal) {
|
||||
connectionsInternal.add(connection.copy())
|
||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
||||
lua.invokeGlobal("onNodeConnectionChange")
|
||||
}
|
||||
}
|
||||
|
||||
fun removeConnection(connection: WireConnection) {
|
||||
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 }
|
||||
|
||||
if (any == true) {
|
||||
otherEntity!!.lua.invokeGlobal("onNodeConnectionChange", 0)
|
||||
otherEntity!!.lua.invokeGlobal("onNodeConnectionChange")
|
||||
}
|
||||
|
||||
any == true
|
||||
}
|
||||
|
||||
if (any)
|
||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
||||
lua.invokeGlobal("onNodeConnectionChange")
|
||||
}
|
||||
}
|
||||
|
||||
fun removeConnectionsTo(pos: Vector2i) {
|
||||
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("sourceId", request.source)
|
||||
|
||||
1
|
||||
}, { getJson() ?: JsonNull.INSTANCE })
|
||||
|
||||
if (result.isPresent) {
|
||||
@ -675,7 +673,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
// break connection if other entity got removed
|
||||
if (connection.otherEntity?.removalReason?.removal == true) {
|
||||
itr.remove()
|
||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
||||
lua.invokeGlobal("onNodeConnectionChange")
|
||||
continue
|
||||
}
|
||||
|
||||
@ -691,7 +689,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
// break connection if we point at invalid node
|
||||
if (otherNode == null) {
|
||||
itr.remove()
|
||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
||||
lua.invokeGlobal("onNodeConnectionChange")
|
||||
} else {
|
||||
newState = newState!! || otherNode.state
|
||||
}
|
||||
@ -706,10 +704,12 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
// otherwise, keep current node state
|
||||
if (newState != null && node.state != newState) {
|
||||
node.state = newState
|
||||
lua.pushTable(hashSize = 2)
|
||||
lua.setTableValue("node", i)
|
||||
lua.setTableValue("level", newState)
|
||||
lua.invokeGlobal("onInputNodeChange", 1)
|
||||
|
||||
lua.invokeGlobal("onInputNodeChange") {
|
||||
pushTable(hashSize = 2)
|
||||
setTableValue("node", i)
|
||||
setTableValue("level", newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -781,9 +781,10 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRemote && reason.dying) {
|
||||
lua.push(health <= 0.0)
|
||||
lua.invokeGlobal("die", 1)
|
||||
if (isLocal && reason.dying) {
|
||||
lua.invokeGlobal("die") {
|
||||
push(health <= 0.0)
|
||||
}
|
||||
|
||||
try {
|
||||
if (doSmash) {
|
||||
|
@ -61,7 +61,7 @@ local function blackboardSet(self, t, key, value)
|
||||
local mappings = self.vectorNumberInput[key]
|
||||
|
||||
if mappings then
|
||||
for _, pair in pairs(input) do
|
||||
for _, pair in pairs(mappings) do
|
||||
local index = pair[1]
|
||||
local tab = pair[2]
|
||||
tab[index] = value
|
||||
@ -107,12 +107,12 @@ function blackboardPrototype:parameters(parameters, nodeID)
|
||||
|
||||
if not typeInput then
|
||||
typeInput = {}
|
||||
self.input[i][pKey] = typeInput
|
||||
self.input[t][pKey] = typeInput
|
||||
end
|
||||
|
||||
table.insert(typeInput, {parameterName, tab})
|
||||
tab[parameterName] = self.board[t][pKey]
|
||||
elseif pValue then
|
||||
elseif pValue ~= nil then
|
||||
if t == 4 then -- vec2
|
||||
-- dumb special case for allowing a vec2 of blackboard number keys
|
||||
if type(pValue) ~= 'table' then
|
||||
@ -132,7 +132,7 @@ function blackboardPrototype:parameters(parameters, nodeID)
|
||||
end
|
||||
|
||||
table.insert(typeInput, {i, vector})
|
||||
vector[i] = self.board[5][key] -- number
|
||||
vector[i] = self.board[5][vValue] -- number
|
||||
else
|
||||
vector[i] = vValue
|
||||
end
|
||||
@ -142,8 +142,6 @@ function blackboardPrototype:parameters(parameters, nodeID)
|
||||
else
|
||||
tab[parameterName] = pValue
|
||||
end
|
||||
else
|
||||
error(string.format('parameter %s of type %s for node %s has no key nor value', parameterName, parameter.type, nodeID))
|
||||
end
|
||||
end
|
||||
|
||||
@ -183,7 +181,7 @@ function blackboardPrototype:clearEphemerals(ephemerals)
|
||||
end
|
||||
end
|
||||
|
||||
local function Blackboard()
|
||||
function Blackboard()
|
||||
return setmetatable({}, blackboardPrototype):ctor()
|
||||
end
|
||||
|
||||
@ -204,6 +202,19 @@ local function runAndReset(self, ...)
|
||||
return status
|
||||
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
|
||||
|
||||
local actionNode = {}
|
||||
@ -221,10 +232,10 @@ function actionNode:ctor(name, parameters, outputs)
|
||||
end
|
||||
|
||||
function actionNode:bake()
|
||||
self.callable = _G[self.name]
|
||||
self.callable = _ENV[self.name]
|
||||
|
||||
if type(callable) ~= 'function' then
|
||||
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable))
|
||||
if type(self.callable) ~= 'function' then
|
||||
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable))
|
||||
end
|
||||
end
|
||||
|
||||
@ -233,7 +244,8 @@ do
|
||||
local resume = coroutine.resume
|
||||
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
|
||||
local status, nodeStatus, nodeExtra
|
||||
|
||||
@ -246,11 +258,13 @@ do
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
if result == nil then
|
||||
if nodeStatus == nil then
|
||||
--table.remove(stack)
|
||||
return RUNNING
|
||||
end
|
||||
|
||||
@ -258,6 +272,8 @@ do
|
||||
blackboard:setOutput(self, nodeExtra)
|
||||
end
|
||||
|
||||
--table.remove(stack)
|
||||
|
||||
if nodeStatus then
|
||||
return SUCCESS
|
||||
else
|
||||
@ -297,10 +313,10 @@ function decoratorNode:ctor(name, parameters, child)
|
||||
end
|
||||
|
||||
function decoratorNode:bake()
|
||||
self.callable = _G[self.name]
|
||||
self.callable = _ENV[self.name]
|
||||
|
||||
if type(callable) ~= 'function' then
|
||||
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable))
|
||||
if type(self.callable) ~= 'function' then
|
||||
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable))
|
||||
end
|
||||
|
||||
self.child:bake()
|
||||
@ -311,7 +327,8 @@ do
|
||||
local resume = coroutine.resume
|
||||
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
|
||||
|
||||
if not self.coroutine then
|
||||
@ -320,7 +337,8 @@ do
|
||||
local status, nodeStatus = resume(coroutine, parameters, blackboard, self.nodeID, delta)
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
@ -329,38 +347,44 @@ do
|
||||
|
||||
if s == 'dead' then
|
||||
-- quite unexpected, but whatever
|
||||
--table.remove(stack)
|
||||
return SUCCESS
|
||||
else
|
||||
self.coroutine = coroutine
|
||||
end
|
||||
elseif nodeStatus then
|
||||
--table.remove(stack)
|
||||
return SUCCESS
|
||||
else
|
||||
--table.remove(stack)
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local childStatus = runAndReset(self.child, delta, blackboard)
|
||||
local childStatus = runAndReset(self.child, delta, blackboard, stack)
|
||||
|
||||
if childStatus == RUNNING then
|
||||
table.remove(stack)
|
||||
return RUNNING
|
||||
end
|
||||
|
||||
local status, nodeStatus = resume(self.coroutine, childStatus)
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
if nodeStatus == nil then
|
||||
-- another yield OR unexpected return?
|
||||
|
||||
local s = coroutine_status(coroutine)
|
||||
local s = coroutine_status(self.coroutine)
|
||||
|
||||
if s == 'dead' then
|
||||
self.coroutine = nil
|
||||
--table.remove(stack)
|
||||
return SUCCESS
|
||||
end
|
||||
else
|
||||
@ -368,8 +392,10 @@ do
|
||||
self.coroutine = nil
|
||||
|
||||
if nodeStatus then
|
||||
--table.remove(stack)
|
||||
return SUCCESS
|
||||
else
|
||||
--table.remove(stack)
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
@ -407,20 +433,29 @@ function seqNode:ctor(children, isSelector)
|
||||
return self
|
||||
end
|
||||
|
||||
function seqNode:run(delta, blackboard)
|
||||
function seqNode:run(delta, blackboard, stack)
|
||||
self.calls = self.calls + 1
|
||||
local size = self.size
|
||||
local isSelector = self.isSelector
|
||||
|
||||
--[[if isSelector then
|
||||
table.insert(stack, 'SelectorNode')
|
||||
else
|
||||
table.insert(stack, 'SequenceNode')
|
||||
end]]
|
||||
|
||||
while self.index <= size do
|
||||
local child = self.children[self.index]
|
||||
local status = runAndReset(child, delta, blackboard)
|
||||
local status = runAndReset(child, delta, blackboard, stack)
|
||||
|
||||
if status == RUNNING then
|
||||
--table.remove(stack)
|
||||
return RUNNING
|
||||
elseif isSelector and status == SUCCESS then
|
||||
--table.remove(stack)
|
||||
return SUCCESS
|
||||
elseif not isSelector and status == FAILURE then
|
||||
--table.remove(stack)
|
||||
return FAILURE
|
||||
end
|
||||
|
||||
@ -464,13 +499,13 @@ parallelNode.__index = parallelNode
|
||||
function parallelNode:ctor(parameters, children)
|
||||
self.children = children
|
||||
|
||||
if type(parameters.success) == 'number' then
|
||||
if type(parameters.success) == 'number' and parameters.success >= 0 then
|
||||
self.successLimit = parameters.success
|
||||
else
|
||||
self.successLimit = #children
|
||||
end
|
||||
|
||||
if type(parameters.fail) == 'number' then
|
||||
if type(parameters.fail) == 'number' and parameters.fail >= 0 then
|
||||
self.failLimit = parameters.fail
|
||||
else
|
||||
self.failLimit = #children
|
||||
@ -483,15 +518,17 @@ function parallelNode:ctor(parameters, children)
|
||||
return self
|
||||
end
|
||||
|
||||
function parallelNode:run(delta, blackboard)
|
||||
function parallelNode:run(delta, blackboard, stack)
|
||||
self.calls = self.calls + 1
|
||||
local failed = 0
|
||||
local succeeded = 0
|
||||
local failLimit = self.failLimit
|
||||
local successLimit = self.successLimit
|
||||
|
||||
--table.insert(stack, 'ParallelNode')
|
||||
|
||||
for _, node in ipairs(self.children) do
|
||||
local status = runAndReset(node, delta, blackboard)
|
||||
local status = runAndReset(node, delta, blackboard, stack)
|
||||
|
||||
if status == SUCCESS then
|
||||
succeeded = succeeded + 1
|
||||
@ -502,16 +539,19 @@ function parallelNode:run(delta, blackboard)
|
||||
if failed >= failLimit then
|
||||
self.lastFailed = failed
|
||||
self.lastSucceed = succeeded
|
||||
--table.remove(stack)
|
||||
return FAILURE
|
||||
elseif succeeded >= successLimit then
|
||||
self.lastFailed = failed
|
||||
self.lastSucceed = succeeded
|
||||
--table.remove(stack)
|
||||
return SUCCESS
|
||||
end
|
||||
end
|
||||
|
||||
self.lastFailed = failed
|
||||
self.lastSucceed = succeeded
|
||||
--table.remove(stack)
|
||||
return RUNNING
|
||||
end
|
||||
|
||||
@ -550,11 +590,12 @@ function dynNode:ctor(children)
|
||||
return self
|
||||
end
|
||||
|
||||
function dynNode:run(delta, blackboard)
|
||||
function dynNode:run(delta, blackboard, stack)
|
||||
self.calls = self.calls + 1
|
||||
--table.insert(stack, 'DynamicNode')
|
||||
|
||||
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
|
||||
self.index = self.index + 1
|
||||
@ -564,10 +605,12 @@ function dynNode:run(delta, blackboard)
|
||||
end
|
||||
|
||||
if status == SUCCESS or self.index > self.size then
|
||||
--table.remove(stack)
|
||||
return status
|
||||
end
|
||||
end
|
||||
|
||||
--table.remove(stack)
|
||||
return RUNNING
|
||||
end
|
||||
|
||||
@ -605,7 +648,7 @@ function randNode:ctor(children)
|
||||
return self
|
||||
end
|
||||
|
||||
function randNode:run(delta, blackboard)
|
||||
function randNode:run(delta, blackboard, stack)
|
||||
self.calls = self.calls + 1
|
||||
|
||||
if self.index == -1 and self.size ~= 0 then
|
||||
@ -615,7 +658,10 @@ function randNode:run(delta, blackboard)
|
||||
if self.index == -1 then
|
||||
return FAILURE
|
||||
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
|
||||
|
||||
@ -654,15 +700,20 @@ function statePrototype:ctor(blackboard, root)
|
||||
end
|
||||
|
||||
function statePrototype:run(delta)
|
||||
local stack = {}
|
||||
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)
|
||||
|
||||
return status
|
||||
end
|
||||
|
||||
function statePrototype:clear()
|
||||
self.tree:reset()
|
||||
self.root:reset()
|
||||
end
|
||||
|
||||
function statePrototype:bake()
|
||||
self.root:bake()
|
||||
end
|
||||
|
||||
function statePrototype:blackboard()
|
||||
|
@ -385,4 +385,72 @@ function mergeJson(base, with)
|
||||
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
|
||||
|
||||
|
@ -99,7 +99,7 @@ local function entityTypeNamesToIntegers(input, fullName)
|
||||
error('invalid entity type ' .. tostring(v) .. ' for ' .. fullName .. ' in types table at index ' .. i, 3)
|
||||
end
|
||||
|
||||
entityTypes[i] = lookup
|
||||
input[i] = lookup
|
||||
end
|
||||
|
||||
return input
|
||||
|
@ -12,12 +12,22 @@ object LuaTests {
|
||||
fun test() {
|
||||
val lua = LuaThread()
|
||||
|
||||
lua.ensureExtraCapacity(1000)
|
||||
lua.push {
|
||||
throw IllegalArgumentException("This is error message")
|
||||
}
|
||||
|
||||
lua.loadGlobal("collectgarbage")
|
||||
lua.push("count")
|
||||
lua.call(1, 1)
|
||||
println(lua.popDouble()!! * 1024)
|
||||
lua.storeGlobal("test")
|
||||
|
||||
lua.call {
|
||||
lua.load("""
|
||||
local function errornous()
|
||||
test()
|
||||
end
|
||||
|
||||
local cor = coroutine.create(errornous)
|
||||
print(coroutine.resume(cor))
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
lua.close()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user