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);
|
jint result = (*env)->CallIntMethod(env, *lua_JCClosure, callback, (long long) state);
|
||||||
|
|
||||||
if (result <= -1) {
|
if (result <= -1) {
|
||||||
const char* errMsg = lua_tostring(state, -1);
|
return lua_error(state);
|
||||||
|
|
||||||
if (errMsg == NULL)
|
|
||||||
return luaL_error(state, "Internal JVM Error");
|
|
||||||
|
|
||||||
return luaL_error(state, "%s", errMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -32,9 +32,9 @@ public interface LuaJNR {
|
|||||||
@IgnoreError
|
@IgnoreError
|
||||||
public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback);
|
public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, @LongLong long ctx, @LongLong long callback);
|
||||||
@IgnoreError
|
@IgnoreError
|
||||||
public int lua_callk(@NotNull Pointer luaState, int numArgs, int numResults, @LongLong long ctx, @LongLong long callback);
|
|
||||||
@IgnoreError
|
|
||||||
public long lua_atpanic(@NotNull Pointer luaState, @LongLong long fn);
|
public long lua_atpanic(@NotNull Pointer luaState, @LongLong long fn);
|
||||||
|
@IgnoreError
|
||||||
|
public long luaL_traceback(@NotNull Pointer luaState, @NotNull Pointer forState, @NotNull String message, int level);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new thread, pushes it on the stack, and returns a pointer to a lua_State that represents this new thread. The new thread returned by this function shares with the original thread its global environment, but has an independent execution stack.
|
* Creates a new thread, pushes it on the stack, and returns a pointer to a lua_State that represents this new thread. The new thread returned by this function shares with the original thread its global environment, but has an independent execution stack.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,4 +17,5 @@ data class BehaviorNodeDefinition(
|
|||||||
*/
|
*/
|
||||||
val properties: ImmutableMap<String, NodeParameter> = ImmutableMap.of(),
|
val properties: ImmutableMap<String, NodeParameter> = ImmutableMap.of(),
|
||||||
val output: ImmutableMap<String, NodeOutput> = ImmutableMap.of(),
|
val output: ImmutableMap<String, NodeOutput> = ImmutableMap.of(),
|
||||||
|
val script: AssetPath? = null,
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,7 @@ import ru.dbotthepony.kstarbound.lua.userdata.NodeParameterType
|
|||||||
data class NodeOutput(val type: NodeParameterType, val key: String? = null, val ephemeral: Boolean = false) {
|
data class NodeOutput(val type: NodeParameterType, val key: String? = null, val ephemeral: Boolean = false) {
|
||||||
fun push(lua: LuaThread) {
|
fun push(lua: LuaThread) {
|
||||||
lua.pushTable(hashSize = 3)
|
lua.pushTable(hashSize = 3)
|
||||||
lua.setTableValue("type", type.ordinal)
|
lua.setTableValue("type", type.ordinal + 1)
|
||||||
|
|
||||||
if (key != null)
|
if (key != null)
|
||||||
lua.setTableValue("key", key)
|
lua.setTableValue("key", key)
|
||||||
|
@ -20,7 +20,7 @@ data class NodeParameter(val type: NodeParameterType, val value: NodeParameterVa
|
|||||||
|
|
||||||
fun push(lua: LuaThread) {
|
fun push(lua: LuaThread) {
|
||||||
lua.pushTable(hashSize = 3)
|
lua.pushTable(hashSize = 3)
|
||||||
lua.setTableValue("type", type.ordinal)
|
lua.setTableValue("type", type.ordinal + 1)
|
||||||
|
|
||||||
if (value.key != null) {
|
if (value.key != null) {
|
||||||
lua.setTableValue("key", value.key)
|
lua.setTableValue("key", value.key)
|
||||||
|
@ -274,7 +274,6 @@ data class ItemDescriptor(
|
|||||||
push(parameters)
|
push(parameters)
|
||||||
push(level)
|
push(level)
|
||||||
push(seed)
|
push(seed)
|
||||||
5
|
|
||||||
}, { getJson() as JsonObject to getJson() as JsonObject }).get()
|
}, { getJson() as JsonObject to getJson() as JsonObject }).get()
|
||||||
} finally {
|
} finally {
|
||||||
lua.close()
|
lua.close()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.json
|
package ru.dbotthepony.kstarbound.json
|
||||||
|
|
||||||
|
import com.github.luben.zstd.ZstdDictDecompress
|
||||||
import com.github.luben.zstd.ZstdInputStreamNoFinalizer
|
import com.github.luben.zstd.ZstdInputStreamNoFinalizer
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
@ -36,7 +37,7 @@ private enum class InflateType {
|
|||||||
NONE
|
NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> ByteArray.callRead(inflate: InflateType, callable: DataInputStream.() -> T): T {
|
private fun <T> ByteArray.callRead(inflate: InflateType, dictionary: ZstdDictDecompress? = null, callable: DataInputStream.() -> T): T {
|
||||||
val stream = FastByteArrayInputStream(this)
|
val stream = FastByteArrayInputStream(this)
|
||||||
|
|
||||||
when (inflate) {
|
when (inflate) {
|
||||||
@ -53,12 +54,17 @@ private fun <T> ByteArray.callRead(inflate: InflateType, callable: DataInputStre
|
|||||||
}
|
}
|
||||||
|
|
||||||
InflateType.ZSTD -> {
|
InflateType.ZSTD -> {
|
||||||
val data = DataInputStream(BufferedInputStream(ZstdInputStreamNoFinalizer(stream), 0x10000))
|
val f = ZstdInputStreamNoFinalizer(stream)
|
||||||
|
|
||||||
|
if (dictionary != null)
|
||||||
|
f.setDict(dictionary)
|
||||||
|
|
||||||
|
val data = DataInputStream(BufferedInputStream(f, 0x10000))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return callable(data)
|
return callable(data)
|
||||||
} finally {
|
} finally {
|
||||||
data.close()
|
f.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,9 +82,9 @@ fun ByteArray.readJsonElementInflated(): JsonElement = callRead(InflateType.ZLIB
|
|||||||
fun ByteArray.readJsonObjectInflated(): JsonObject = callRead(InflateType.ZLIB) { readJsonObject() }
|
fun ByteArray.readJsonObjectInflated(): JsonObject = callRead(InflateType.ZLIB) { readJsonObject() }
|
||||||
fun ByteArray.readJsonArrayInflated(): JsonArray = callRead(InflateType.ZLIB) { readJsonArray() }
|
fun ByteArray.readJsonArrayInflated(): JsonArray = callRead(InflateType.ZLIB) { readJsonArray() }
|
||||||
|
|
||||||
fun ByteArray.readJsonElementZstd(): JsonElement = callRead(InflateType.ZSTD) { readJsonElement() }
|
fun ByteArray.readJsonElementZstd(dictionary: ZstdDictDecompress? = null): JsonElement = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonElement() }
|
||||||
fun ByteArray.readJsonObjectZstd(): JsonObject = callRead(InflateType.ZSTD) { readJsonObject() }
|
fun ByteArray.readJsonObjectZstd(dictionary: ZstdDictDecompress? = null): JsonObject = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonObject() }
|
||||||
fun ByteArray.readJsonArrayZstd(): JsonArray = callRead(InflateType.ZSTD) { readJsonArray() }
|
fun ByteArray.readJsonArrayZstd(dictionary: ZstdDictDecompress? = null): JsonArray = callRead(InflateType.ZSTD, dictionary = dictionary) { readJsonArray() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Позволяет читать двоичный JSON прямиком в [JsonElement]
|
* Позволяет читать двоичный JSON прямиком в [JsonElement]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.json
|
package ru.dbotthepony.kstarbound.json
|
||||||
|
|
||||||
|
import com.github.luben.zstd.ZstdDictCompress
|
||||||
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
|
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
@ -21,7 +22,7 @@ private enum class DeflateType {
|
|||||||
NONE
|
NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6, callable: DataOutputStream.(T) -> Unit): ByteArray {
|
private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6, zstdDictionary: ZstdDictCompress? = null, callable: DataOutputStream.(T) -> Unit): ByteArray {
|
||||||
val stream = FastByteArrayOutputStream()
|
val stream = FastByteArrayOutputStream()
|
||||||
|
|
||||||
when (deflate) {
|
when (deflate) {
|
||||||
@ -38,6 +39,9 @@ private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6,
|
|||||||
val s = ZstdOutputStreamNoFinalizer(stream)
|
val s = ZstdOutputStreamNoFinalizer(stream)
|
||||||
s.setLevel(zstdCompressionLevel)
|
s.setLevel(zstdCompressionLevel)
|
||||||
|
|
||||||
|
if (zstdDictionary != null)
|
||||||
|
s.setDict(zstdDictionary)
|
||||||
|
|
||||||
DataOutputStream(BufferedOutputStream(s, 0x10000)).use {
|
DataOutputStream(BufferedOutputStream(s, 0x10000)).use {
|
||||||
callable(it, this)
|
callable(it, this)
|
||||||
}
|
}
|
||||||
@ -57,9 +61,9 @@ fun JsonElement.writeJsonElementDeflated(): ByteArray = callWrite(DeflateType.ZL
|
|||||||
fun JsonObject.writeJsonObjectDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonObject(it) }
|
fun JsonObject.writeJsonObjectDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonObject(it) }
|
||||||
fun JsonArray.writeJsonArrayDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonArray(it) }
|
fun JsonArray.writeJsonArrayDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonArray(it) }
|
||||||
|
|
||||||
fun JsonElement.writeJsonElementZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonElement(it) }
|
fun JsonElement.writeJsonElementZstd(level: Int = 6, dictionary: ZstdDictCompress? = null): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level, zstdDictionary = dictionary) { writeJsonElement(it) }
|
||||||
fun JsonObject.writeJsonObjectZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonObject(it) }
|
fun JsonObject.writeJsonObjectZstd(level: Int = 6, dictionary: ZstdDictCompress? = null): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level, zstdDictionary = dictionary) { writeJsonObject(it) }
|
||||||
fun JsonArray.writeJsonArrayZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonArray(it) }
|
fun JsonArray.writeJsonArrayZstd(level: Int = 6, dictionary: ZstdDictCompress? = null): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level, zstdDictionary = dictionary) { writeJsonArray(it) }
|
||||||
|
|
||||||
fun DataOutputStream.writeJsonElement(value: JsonElement) {
|
fun DataOutputStream.writeJsonElement(value: JsonElement) {
|
||||||
when (value) {
|
when (value) {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua
|
package ru.dbotthepony.kstarbound.lua
|
||||||
|
|
||||||
data class CommonHandleRegistry(
|
data class CommonHandleRegistry(
|
||||||
val future: LuaHandle,
|
val future: LuaHandle = LuaHandle.Nil,
|
||||||
val pathFinder: LuaHandle,
|
val pathFinder: LuaHandle = LuaHandle.Nil,
|
||||||
)
|
) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY = CommonHandleRegistry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import ru.dbotthepony.kstarbound.math.Line2d
|
|||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2f
|
import ru.dbotthepony.kstarbound.math.vector.Vector2f
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.util.floorToInt
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
// TODO: error reporting when argument was provided, but it is malformed
|
// TODO: error reporting when argument was provided, but it is malformed
|
||||||
@ -176,10 +177,10 @@ fun LuaThread.getVector2iOrAABB(stackIndex: Int = -1): Either<Vector2i, AABB>? {
|
|||||||
|
|
||||||
fun LuaThread.ArgStack.nextVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB> {
|
fun LuaThread.ArgStack.nextVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB> {
|
||||||
if (position !in 1 ..this.top)
|
if (position !in 1 ..this.top)
|
||||||
throw IllegalArgumentException("bad argument #$position: Vector2d expected, got nil")
|
throw IllegalArgumentException("bad argument #$position: Vector2i expected, got nil")
|
||||||
|
|
||||||
return lua.getVector2iOrAABB(position)
|
return lua.getVector2iOrAABB(position)
|
||||||
?: throw IllegalArgumentException("bad argument #$position: Vector2d or AABB expected, got ${lua.typeAt(position)}")
|
?: throw IllegalArgumentException("bad argument #$position: Vector2i or AABB expected, got ${lua.typeAt(position)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.ArgStack.nextOptionalVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB>? {
|
fun LuaThread.ArgStack.nextOptionalVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB>? {
|
||||||
@ -223,18 +224,20 @@ fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? {
|
|||||||
push(1)
|
push(1)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val x = getLong()
|
// FIXME: original engine parity, where it casts doubles into ints
|
||||||
|
// while it seems okay, it can cause undesired side effects
|
||||||
|
val x = getDouble()
|
||||||
pop()
|
pop()
|
||||||
x ?: return null
|
x ?: return null
|
||||||
|
|
||||||
push(2)
|
push(2)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val y = getLong()
|
val y = getDouble()
|
||||||
pop()
|
pop()
|
||||||
y ?: return null
|
y ?: return null
|
||||||
|
|
||||||
return Vector2i(x.toInt(), y.toInt())
|
return Vector2i(x.floorToInt(), y.floorToInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.ArgStack.nextVector2i(position: Int = this.position++): Vector2i {
|
fun LuaThread.ArgStack.nextVector2i(position: Int = this.position++): Vector2i {
|
||||||
@ -262,31 +265,31 @@ fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? {
|
|||||||
push(1)
|
push(1)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val x = getLong()
|
val x = getFloat()
|
||||||
pop()
|
pop()
|
||||||
x ?: return null
|
x ?: return null
|
||||||
|
|
||||||
push(2)
|
push(2)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val y = getLong()
|
val y = getFloat()
|
||||||
pop()
|
pop()
|
||||||
y ?: return null
|
y ?: return null
|
||||||
|
|
||||||
push(3)
|
push(3)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val z = getLong()
|
val z = getFloat()
|
||||||
pop()
|
pop()
|
||||||
z ?: return null
|
z ?: return null
|
||||||
|
|
||||||
push(4)
|
push(4)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val w = getLong() ?: 255L
|
val w = getFloat() ?: 255f
|
||||||
pop()
|
pop()
|
||||||
|
|
||||||
return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt())
|
return RGBAColor(x / 255f, y / 255f, z / 255f, w / 255f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.ArgStack.nextColor(position: Int = this.position++): RGBAColor {
|
fun LuaThread.ArgStack.nextColor(position: Int = this.position++): RGBAColor {
|
||||||
@ -365,32 +368,33 @@ fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? {
|
|||||||
push(1)
|
push(1)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val x = getLong()
|
// FIXME: original engine parity
|
||||||
|
val x = getDouble()
|
||||||
pop()
|
pop()
|
||||||
x ?: return null
|
x ?: return null
|
||||||
|
|
||||||
push(2)
|
push(2)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val y = getLong()
|
val y = getDouble()
|
||||||
pop()
|
pop()
|
||||||
y ?: return null
|
y ?: return null
|
||||||
|
|
||||||
push(3)
|
push(3)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val z = getLong()
|
val z = getDouble()
|
||||||
pop()
|
pop()
|
||||||
z ?: return null
|
z ?: return null
|
||||||
|
|
||||||
push(4)
|
push(4)
|
||||||
loadTableValue(abs)
|
loadTableValue(abs)
|
||||||
|
|
||||||
val w = getLong()
|
val w = getDouble()
|
||||||
pop()
|
pop()
|
||||||
w ?: return null
|
w ?: return null
|
||||||
|
|
||||||
return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt()))
|
return AABBi(Vector2i(x.floorToInt(), y.floorToInt()), Vector2i(z.floorToInt(), w.floorToInt()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.ArgStack.nextAABBi(position: Int = this.position++): AABBi {
|
fun LuaThread.ArgStack.nextAABBi(position: Int = this.position++): AABBi {
|
||||||
|
@ -16,17 +16,6 @@ const val LUA_ERRMEM = 4
|
|||||||
const val LUA_ERRERR = 5
|
const val LUA_ERRERR = 5
|
||||||
|
|
||||||
class InvalidLuaSyntaxException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
class InvalidLuaSyntaxException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||||
class LuaMemoryAllocException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
class LuaMemoryAllocException(message: String? = null, cause: Throwable? = null) : Error(message, cause)
|
||||||
class LuaGCException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
|
||||||
class LuaException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
class LuaException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||||
class LuaRuntimeException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
class LuaRuntimeException(message: String? = null, cause: Throwable? = null, writeStackTrace: Boolean = true) : RuntimeException(message, cause, true, writeStackTrace)
|
||||||
|
|
||||||
fun throwPcallError(code: Int) {
|
|
||||||
when (code) {
|
|
||||||
LUA_OK -> {}
|
|
||||||
LUA_ERRRUN -> throw LuaException("Runtime Error")
|
|
||||||
LUA_ERRMEM -> throw LuaMemoryAllocException()
|
|
||||||
LUA_ERRERR -> throw LuaException("Exception inside Exception handler")
|
|
||||||
else -> throw LuaException("Unknown Lua Loading error: $code")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,36 +1,169 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua
|
package ru.dbotthepony.kstarbound.lua
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonNull
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.lang.ref.Cleaner.Cleanable
|
import java.lang.ref.Cleaner.Cleanable
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
class LuaHandle(private val parent: LuaSharedState, val handle: Int, val key: Any?) : Closeable {
|
interface LuaHandle : Closeable {
|
||||||
private val cleanable: Cleanable
|
fun push(into: LuaThread)
|
||||||
|
override fun close() {}
|
||||||
|
fun toJson(): JsonElement
|
||||||
|
val type: LuaType
|
||||||
|
|
||||||
var isValid = true
|
object Nil : LuaHandle {
|
||||||
private set
|
override fun push(into: LuaThread) {
|
||||||
|
into.push()
|
||||||
init {
|
|
||||||
val parent = parent
|
|
||||||
val handle = handle
|
|
||||||
val key = key
|
|
||||||
|
|
||||||
cleanable = Starbound.CLEANER.register(this) {
|
|
||||||
parent.freeHandle(handle, key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
object True : LuaHandle {
|
||||||
check(isValid) { "Tried to use NULL handle!" }
|
override fun push(into: LuaThread) {
|
||||||
parent.handlesThread.push()
|
into.push(true)
|
||||||
parent.handlesThread.copy(handle, -1)
|
}
|
||||||
parent.handlesThread.moveStackValuesOnto(into)
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "LuaHandle.True"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toJson(): JsonElement {
|
||||||
|
return InternedJsonElementAdapter.TRUE
|
||||||
|
}
|
||||||
|
|
||||||
|
override val type: LuaType
|
||||||
|
get() = LuaType.BOOLEAN
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
object False : LuaHandle {
|
||||||
if (!isValid) return
|
override fun push(into: LuaThread) {
|
||||||
cleanable.clean()
|
into.push(false)
|
||||||
isValid = 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]
|
return handlers[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun handle(message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? {
|
fun handle(message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? {
|
||||||
val handler = lookupHandler(message) ?: return null
|
val handler = lookupHandler(message) ?: return null
|
||||||
val top = lua.stackTop
|
val top = lua.stackTop
|
||||||
|
|
||||||
try {
|
try {
|
||||||
lua.push(handler)
|
return lua.call(1, {
|
||||||
lua.push(isLocal)
|
push(handler)
|
||||||
val amountOfArguments = arguments(lua)
|
push(isLocal)
|
||||||
check(amountOfArguments >= 0) { "Invalid amount of arguments to pass to Lua handler: $amountOfArguments" }
|
arguments(lua)
|
||||||
|
}, { getJson(it) })
|
||||||
lua.call(amountOfArguments + 1, 1)
|
|
||||||
|
|
||||||
return lua.getJson()
|
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
if (logPacer.consumeAndReturnDeadline() <= 0L)
|
if (logPacer.consumeAndReturnDeadline() <= 0L)
|
||||||
LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err)
|
LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err)
|
||||||
|
@ -30,11 +30,24 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
|||||||
check(isValid) { "Tried to use NULL LuaState!" }
|
check(isValid) { "Tried to use NULL LuaState!" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errorToStringFunction by Delegates.notNull<LuaHandle>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var errorTrapFunction by Delegates.notNull<LuaHandle>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "LuaSharedState[$handlesThread]"
|
||||||
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
if (!isValid) return
|
if (!isValid) return
|
||||||
isValid = false
|
isValid = false
|
||||||
namedHandles.clear()
|
namedHandles.clear()
|
||||||
cleanable.clean()
|
cleanable.clean()
|
||||||
|
errorToStringFunction = LuaHandle.Nil
|
||||||
|
errorTrapFunction = LuaHandle.Nil
|
||||||
|
commonHandles = CommonHandleRegistry.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initializeHandles(mainThread: LuaThread) {
|
fun initializeHandles(mainThread: LuaThread) {
|
||||||
@ -45,6 +58,36 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
|||||||
future = future,
|
future = future,
|
||||||
pathFinder = pathFinder,
|
pathFinder = pathFinder,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
handlesThread.push {
|
||||||
|
//it.lua.push(it.nextObject<Throwable?>(-1)?.stackTraceToString() ?: it.nextObject<Any?>(-1).toString())
|
||||||
|
it.lua.push(it.nextObject<Any?>().toString())
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
errorToStringFunction = allocateHandle(null)
|
||||||
|
|
||||||
|
handlesThread.push {
|
||||||
|
val peek = it.peek()
|
||||||
|
|
||||||
|
if (peek == LuaType.STRING) {
|
||||||
|
it.lua.traceback(it.lua.getString()!!, 1)
|
||||||
|
val err = LuaRuntimeException(it.lua.getString())
|
||||||
|
it.lua.push(err)
|
||||||
|
} else if (peek == LuaType.USERDATA) {
|
||||||
|
val obj = it.nextObject<Any>()
|
||||||
|
|
||||||
|
if (obj is Throwable && obj !is LuaRuntimeException) {
|
||||||
|
it.lua.traceback(obj.toString(), 1)
|
||||||
|
val err = LuaRuntimeException(it.lua.getString(), cause = obj, writeStackTrace = false)
|
||||||
|
it.lua.push(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
errorTrapFunction = allocateHandle(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun freeHandle(handle: Int, key: Any?) {
|
fun freeHandle(handle: Int, key: Any?) {
|
||||||
@ -79,10 +122,10 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
|||||||
|
|
||||||
if (freeHandles.isEmpty()) {
|
if (freeHandles.isEmpty()) {
|
||||||
if (nextHandle % 10 == 0) {
|
if (nextHandle % 10 == 0) {
|
||||||
handlesThread.ensureExtraCapacity(10)
|
handlesThread.ensureExtraCapacity(20)
|
||||||
}
|
}
|
||||||
|
|
||||||
return LuaHandle(this, ++nextHandle, name).also {
|
return LuaHandle.Regular(this, ++nextHandle, name).also {
|
||||||
if (name != null) namedHandles[name] = it
|
if (name != null) namedHandles[name] = it
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -92,7 +135,7 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana
|
|||||||
handlesThread.copy(-1, handle)
|
handlesThread.copy(-1, handle)
|
||||||
handlesThread.pop()
|
handlesThread.pop()
|
||||||
|
|
||||||
return LuaHandle(this, handle, name).also {
|
return LuaHandle.Regular(this, handle, name).also {
|
||||||
if (name != null) namedHandles[name] = it
|
if (name != null) namedHandles[name] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,14 +90,19 @@ class LuaThread private constructor(
|
|||||||
this.storeGlobal("math")
|
this.storeGlobal("math")
|
||||||
LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
|
LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
|
||||||
this.storeGlobal("utf8")
|
this.storeGlobal("utf8")
|
||||||
|
LuaJNR.INSTANCE.luaopen_debug(this.pointer)
|
||||||
|
this.storeGlobal("debug")
|
||||||
|
LuaJNR.INSTANCE.luaopen_os(this.pointer)
|
||||||
|
this.storeGlobal("os")
|
||||||
|
|
||||||
sharedState.initializeHandles(this)
|
sharedState.initializeHandles(this)
|
||||||
|
|
||||||
provideUtilityBindings(this)
|
provideUtilityBindings(this)
|
||||||
provideRootBindings(this)
|
provideRootBindings(this)
|
||||||
|
|
||||||
load(globalScript, "@/internal/global.lua")
|
call {
|
||||||
call()
|
load(globalScript, "@/internal/global.lua")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun interface Fn {
|
fun interface Fn {
|
||||||
@ -204,115 +209,219 @@ class LuaThread private constructor(
|
|||||||
closure.dispose()
|
closure.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun call(numArgs: Int = 0, numResults: Int = 0): Int {
|
private fun pcall(numArgs: Int, numResults: Int, handler: Int) {
|
||||||
sharedState.cleanup()
|
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, handler, 0L, 0L)
|
||||||
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
|
|
||||||
|
|
||||||
if (status == LUA_ERRRUN) {
|
if (status == LUA_ERRRUN) {
|
||||||
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun callConditional(block: LuaThread.() -> Boolean) {
|
||||||
* Returns boolean indicating whenever function exists
|
sharedState.cleanup()
|
||||||
*/
|
|
||||||
inline fun invokeGlobal(name: String, arguments: LuaThread.() -> Int): Boolean {
|
|
||||||
val top = stackTop
|
val top = stackTop
|
||||||
|
push(sharedState.errorTrapFunction)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val type = loadGlobal(name)
|
if (block(this)) {
|
||||||
if (type != LuaType.FUNCTION)
|
val newTop = stackTop
|
||||||
return false
|
|
||||||
|
|
||||||
val numArguments = arguments(this)
|
if (newTop == top + 1) {
|
||||||
check(numArguments >= 0) { "Invalid amount of arguments provided to Lua function" }
|
throw IllegalArgumentException("No function was pushed to stack")
|
||||||
call(numArguments)
|
}
|
||||||
return true
|
|
||||||
|
pcall(newTop - top - 2, 0, top + 1)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setTop(top)
|
LuaJNR.INSTANCE.lua_settop(pointer, top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun call(block: LuaThread.() -> Unit) {
|
||||||
* Returns boolean indicating whenever function exists
|
return callConditional { block(this); true }
|
||||||
*/
|
}
|
||||||
fun invokeGlobal(name: String, numArguments: Int): Boolean {
|
|
||||||
|
fun <T> callConditional(numResults: Int, block: LuaThread.() -> Boolean, resultHandler: LuaThread.(firstStackIndex: Int) -> T): KOptional<T> {
|
||||||
|
require(numResults >= 0) { "Invalid amount of results to get from Lua call: $numResults" }
|
||||||
|
sharedState.cleanup()
|
||||||
|
|
||||||
val top = stackTop
|
val top = stackTop
|
||||||
|
push(sharedState.errorTrapFunction)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val type = loadGlobal(name)
|
if (block(this)) {
|
||||||
|
val newTop = stackTop
|
||||||
|
|
||||||
if (type != LuaType.FUNCTION) {
|
if (newTop == top + 1) {
|
||||||
pop(numArguments)
|
throw IllegalArgumentException("No function was pushed to stack")
|
||||||
return false
|
}
|
||||||
|
|
||||||
|
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)
|
pcall(newTop - top - 2, numResults, top + 1)
|
||||||
return true
|
|
||||||
|
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 {
|
} finally {
|
||||||
setTop(top)
|
LuaJNR.INSTANCE.lua_settop(pointer, top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun call(numResults: Int, block: LuaThread.() -> Unit): List<LuaHandle> {
|
||||||
* Returns empty [KOptional] if function does not exist
|
return callConditional(numResults, { block(this); true })!!
|
||||||
*/
|
}
|
||||||
inline fun <T> invokeGlobal(name: String, numResults: Int, arguments: LuaThread.() -> Int, results: LuaThread.(firstValue: Int) -> T): KOptional<T> {
|
|
||||||
require(numResults > 0) { "Invalid amount of results: $numResults" }
|
|
||||||
val top = stackTop
|
|
||||||
|
|
||||||
try {
|
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)
|
val type = loadGlobal(name)
|
||||||
if (type != LuaType.FUNCTION)
|
if (type != LuaType.FUNCTION)
|
||||||
return KOptional()
|
return@callConditional false
|
||||||
|
|
||||||
val numArguments = arguments(this)
|
exists = true
|
||||||
call(numArguments, numResults)
|
return@callConditional true
|
||||||
return KOptional(results(this, top + 1))
|
}
|
||||||
} finally {
|
|
||||||
setTop(top)
|
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 {
|
fun <T> invokeGlobal(name: String, numResults: Int, arguments: LuaThread.() -> Unit, resultHandler: LuaThread.(firstStackIndex: Int) -> T): KOptional<T> {
|
||||||
require(numResults > 0) { "Invalid amount of results: $numResults" }
|
return callConditional(numResults, {
|
||||||
|
val type = loadGlobal(name)
|
||||||
|
if (type != LuaType.FUNCTION)
|
||||||
|
return@callConditional false
|
||||||
|
|
||||||
val top = stackTop
|
arguments(this)
|
||||||
|
return@callConditional true
|
||||||
|
}, resultHandler)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
fun <T> eval(chunk: String, name: String = "eval", numResults: Int, arguments: LuaThread.() -> Unit, results: LuaThread.(firstValue: Int) -> T): T {
|
||||||
val numArguments = arguments(this)
|
return call(numResults, {
|
||||||
load(chunk, name)
|
load(chunk, name)
|
||||||
call(numArguments, numResults)
|
arguments(this)
|
||||||
return results(this, top + 1)
|
}, results)
|
||||||
} finally {
|
}
|
||||||
setTop(top)
|
|
||||||
|
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) {
|
fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Unit) {
|
||||||
val top = stackTop
|
return call {
|
||||||
|
|
||||||
try {
|
|
||||||
val numArguments = arguments(this)
|
|
||||||
load(chunk, name)
|
load(chunk, name)
|
||||||
call(numArguments, 0)
|
arguments(this)
|
||||||
} finally {
|
|
||||||
setTop(top)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun eval(chunk: String, name: String = "eval"): JsonElement {
|
fun eval(chunk: String, name: String = "eval"): JsonElement {
|
||||||
val top = stackTop
|
return call(1, {
|
||||||
|
|
||||||
try {
|
|
||||||
load(chunk, name)
|
load(chunk, name)
|
||||||
call(numResults = 1)
|
}, { getJson(it)!! })
|
||||||
return getJson() ?: JsonNull.INSTANCE
|
|
||||||
} finally {
|
|
||||||
setTop(top)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val attachedScripts = ArrayList<String>()
|
private val attachedScripts = ArrayList<String>()
|
||||||
@ -333,8 +442,9 @@ class LuaThread private constructor(
|
|||||||
try {
|
try {
|
||||||
// minor hiccups during unpopulated script cache should be tolerable
|
// minor hiccups during unpopulated script cache should be tolerable
|
||||||
for ((chunk, path) in loadScripts) {
|
for ((chunk, path) in loadScripts) {
|
||||||
load(chunk.join(), "@$path")
|
call {
|
||||||
call()
|
load(chunk.join(), "@$path")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
LOGGER.error("Failed to attach scripts to Lua environment", err)
|
LOGGER.error("Failed to attach scripts to Lua environment", err)
|
||||||
@ -343,16 +453,18 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (callInit) {
|
if (callInit) {
|
||||||
val type = loadGlobal("init")
|
callConditional {
|
||||||
|
val type = loadGlobal("init")
|
||||||
|
|
||||||
if (type == LuaType.FUNCTION) {
|
if (type == LuaType.NIL || type == LuaType.NONE) {
|
||||||
call()
|
return@callConditional false
|
||||||
} else if (type == LuaType.NIL || type == LuaType.NONE) {
|
} else if (type != LuaType.FUNCTION) {
|
||||||
pop()
|
throw LuaRuntimeException("init is not a function: $type")
|
||||||
} else {
|
}
|
||||||
pop()
|
|
||||||
throw LuaRuntimeException("init is not a function: $type")
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
LOGGER.error("Failed to call init() in Lua environment", err)
|
LOGGER.error("Failed to call init() in Lua environment", err)
|
||||||
@ -371,8 +483,9 @@ class LuaThread private constructor(
|
|||||||
sharedState.ensureValid()
|
sharedState.ensureValid()
|
||||||
if (initCalled) {
|
if (initCalled) {
|
||||||
// minor hiccups during unpopulated script cache should be tolerable
|
// minor hiccups during unpopulated script cache should be tolerable
|
||||||
load(Starbound.readLuaScript(script).join(), "@$script")
|
call {
|
||||||
call()
|
load(Starbound.readLuaScript(script).join(), "@$script")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
attachedScripts.add(script)
|
attachedScripts.add(script)
|
||||||
}
|
}
|
||||||
@ -710,8 +823,8 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
||||||
keyVisitor(this, abs + 1)
|
keyVisitor(this, top)
|
||||||
valueVisitor(this, abs + 2)
|
valueVisitor(this, top + 1)
|
||||||
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -734,7 +847,7 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
||||||
values.add(keyVisitor(this, abs + 1))
|
values.add(keyVisitor(this, top))
|
||||||
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -757,7 +870,7 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
||||||
values.add(valueVisitor(this, abs + 2))
|
values.add(valueVisitor(this, top + 1))
|
||||||
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -780,7 +893,7 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) {
|
||||||
values.add(keyVisitor(this, abs + 1) to valueVisitor(this, abs + 2))
|
values.add(keyVisitor(this, top) to valueVisitor(this, top + 1))
|
||||||
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -1106,7 +1219,7 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun closure(p: Long, function: Fn, performanceCritical: Boolean): Int {
|
private fun closure(p: Long, function: Fn): Int {
|
||||||
sharedState.cleanup()
|
sharedState.cleanup()
|
||||||
val realLuaState: LuaThread
|
val realLuaState: LuaThread
|
||||||
|
|
||||||
@ -1118,69 +1231,33 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val args = realLuaState.ArgStack(realLuaState.stackTop)
|
val args = realLuaState.ArgStack(realLuaState.stackTop)
|
||||||
val rememberStack: ArrayList<String>?
|
|
||||||
|
|
||||||
if (performanceCritical) {
|
|
||||||
rememberStack = null
|
|
||||||
} else {
|
|
||||||
rememberStack = ArrayList(Exception().stackTraceToString().split('\n'))
|
|
||||||
|
|
||||||
rememberStack.removeAt(0) // java.lang. ...
|
|
||||||
// rememberStack.removeAt(0) // at ... push( ... )
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val value = function.invoke(args)
|
val value = function.invoke(args)
|
||||||
check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" }
|
check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" }
|
||||||
return value
|
return value
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
try {
|
realLuaState.push(err)
|
||||||
if (performanceCritical) {
|
return -1
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
sharedState.ensureValid()
|
||||||
|
|
||||||
LuaJNI.lua_pushcclosure(pointer.address()) {
|
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>) {
|
fun <T> push(self: T, function: Binding<T>) {
|
||||||
push {
|
push {
|
||||||
function.invoke(self, it)
|
function.invoke(self, it)
|
||||||
@ -1363,6 +1440,17 @@ class LuaThread private constructor(
|
|||||||
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
|
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun swap(indexA: Int, indexB: Int) {
|
||||||
|
if (indexA == indexB) return
|
||||||
|
val absA = if (indexA < 0) indexA - 1 else indexA
|
||||||
|
val absB = if (indexB < 0) indexB - 1 else indexB
|
||||||
|
push()
|
||||||
|
copy(absA, -1)
|
||||||
|
copy(absB, absA)
|
||||||
|
copy(-1, absB)
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
|
||||||
fun dup() {
|
fun dup() {
|
||||||
push()
|
push()
|
||||||
copy(-2, -1)
|
copy(-2, -1)
|
||||||
@ -1590,8 +1678,8 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is JsonArray -> {
|
is JsonArray -> {
|
||||||
this.loadGlobal("jarray")
|
val handle = call(1) { loadGlobal("jarray") }.first()
|
||||||
this.call(numResults = 1)
|
handle.push(this)
|
||||||
val index = this.stackTop
|
val index = this.stackTop
|
||||||
|
|
||||||
for ((i, v) in value.withIndex()) {
|
for ((i, v) in value.withIndex()) {
|
||||||
@ -1603,8 +1691,8 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is JsonObject -> {
|
is JsonObject -> {
|
||||||
this.loadGlobal("jobject")
|
val handle = call(1) { loadGlobal("jobject") }.first()
|
||||||
this.call(numResults = 1)
|
handle.push(this)
|
||||||
|
|
||||||
val index = this.stackTop
|
val index = this.stackTop
|
||||||
|
|
||||||
@ -1622,6 +1710,10 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "LuaThread at ${pointer.address()}"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val LOGGER = LogManager.getLogger()
|
val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
|
@ -38,23 +38,26 @@ class LuaUpdateComponent(val lua: LuaThread, val name: Any) {
|
|||||||
if (steps >= stepCount) {
|
if (steps >= stepCount) {
|
||||||
steps %= stepCount
|
steps %= stepCount
|
||||||
|
|
||||||
val type = lua.loadGlobal("update")
|
lua.callConditional {
|
||||||
|
val type = loadGlobal("update")
|
||||||
|
|
||||||
if (type != lastType) {
|
/*if (type != lastType) {
|
||||||
lastType = type
|
lastType = type
|
||||||
|
|
||||||
if (type != LuaType.FUNCTION) {
|
if (type != LuaType.FUNCTION) {
|
||||||
LOGGER.warn("Lua environment for $name has $type as global 'update', script update wasn't called")
|
LOGGER.warn("Lua environment for $name has $type as global 'update', script update wasn't called")
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (type == LuaType.FUNCTION) {
|
||||||
|
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 {
|
private fun toAbsolutePosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
|
||||||
args.lua.push(self.movement.getAbsolutePosition(args.nextVector2d()))
|
args.lua.push(self.movement.getAbsolutePosition(args.nextVector2d()))
|
||||||
return 0
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mouthPosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
|
private fun mouthPosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
|
||||||
args.lua.push(self.mouthPosition)
|
args.lua.push(self.mouthPosition)
|
||||||
return 0
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// This callback is registered here rather than in
|
// This callback is registered here rather than in
|
||||||
|
@ -386,6 +386,7 @@ fun provideServerWorldBindings(self: ServerWorld, lua: LuaThread) {
|
|||||||
lua.setTableValueToStub("setLayerEnvironmentBiome")
|
lua.setTableValueToStub("setLayerEnvironmentBiome")
|
||||||
lua.setTableValueToStub("setPlanetType")
|
lua.setTableValueToStub("setPlanetType")
|
||||||
|
|
||||||
lua.load(script, "@/internal/server_world.lua")
|
lua.call {
|
||||||
lua.call()
|
load(script, "@/internal/server_world.lua")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,91 +160,89 @@ private fun printJson(args: LuaThread.ArgStack): Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun provideUtilityBindings(lua: LuaThread) {
|
fun provideUtilityBindings(lua: LuaThread) {
|
||||||
with(lua) {
|
lua.push {
|
||||||
push {
|
LuaThread.LOGGER.info(it.nextString())
|
||||||
LuaThread.LOGGER.info(it.nextString())
|
0
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
storeGlobal("__print")
|
|
||||||
|
|
||||||
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.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.pushTable()
|
||||||
lua.dup()
|
lua.dup()
|
||||||
lua.storeGlobal("sb")
|
lua.storeGlobal("sb")
|
||||||
|
@ -943,6 +943,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaThread) {
|
|||||||
|
|
||||||
lua.pop()
|
lua.pop()
|
||||||
|
|
||||||
lua.load(worldScript, "@/internal/world.lua")
|
lua.call {
|
||||||
lua.call()
|
load(worldScript, "@/internal/world.lua")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.lua.bindings
|
|||||||
|
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
@ -96,7 +97,17 @@ private data class CallScriptData(
|
|||||||
|
|
||||||
private fun LuaThread.ArgStack.getScriptData(): CallScriptData? {
|
private fun LuaThread.ArgStack.getScriptData(): CallScriptData? {
|
||||||
if (peek() == LuaType.STRING) {
|
if (peek() == LuaType.STRING) {
|
||||||
return CallScriptData(nextString(), nextJson().asJsonArray, nextJson())
|
val name = nextString()
|
||||||
|
val nextJson = nextJson()
|
||||||
|
val expected = nextJson()
|
||||||
|
|
||||||
|
if (nextJson is JsonObject && nextJson.size() == 0) {
|
||||||
|
return CallScriptData(name, JsonArray(), expected)
|
||||||
|
} else if (nextJson is JsonArray) {
|
||||||
|
return CallScriptData(name, nextJson, expected)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Invalid script arguments to use (expected to be an array): $nextJson")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
skip(3)
|
skip(3)
|
||||||
return null
|
return null
|
||||||
|
@ -29,7 +29,7 @@ private fun name(self: WorldObject, args: LuaThread.ArgStack): Int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun directions(self: WorldObject, args: LuaThread.ArgStack): Int {
|
private fun direction(self: WorldObject, args: LuaThread.ArgStack): Int {
|
||||||
args.lua.push(self.direction.numericalValue)
|
args.lua.push(self.direction.numericalValue)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -320,7 +320,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaThread) {
|
|||||||
lua.storeGlobal("object")
|
lua.storeGlobal("object")
|
||||||
|
|
||||||
lua.pushBinding(self, "name", ::name)
|
lua.pushBinding(self, "name", ::name)
|
||||||
lua.pushBinding(self, "directions", ::directions)
|
lua.pushBinding(self, "direction", ::direction)
|
||||||
lua.pushBinding(self, "position", ::position)
|
lua.pushBinding(self, "position", ::position)
|
||||||
lua.pushBinding(self, "setInteractive", ::setInteractive)
|
lua.pushBinding(self, "setInteractive", ::setInteractive)
|
||||||
lua.pushBinding(self, "uniqueId", ::uniqueId)
|
lua.pushBinding(self, "uniqueId", ::uniqueId)
|
||||||
|
@ -29,7 +29,7 @@ private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Ma
|
|||||||
if (parameter.key != null)
|
if (parameter.key != null)
|
||||||
str = parameter.key
|
str = parameter.key
|
||||||
|
|
||||||
// original engine does this, and i don't know why this make any sense
|
// FIXME: original engine does this, and i don't know why this make any sense
|
||||||
else if (parameter.value is JsonPrimitive && parameter.value.isString)
|
else if (parameter.value is JsonPrimitive && parameter.value.isString)
|
||||||
str = parameter.value.asString
|
str = parameter.value.asString
|
||||||
|
|
||||||
@ -144,7 +144,12 @@ private fun createNode(
|
|||||||
functions.add(name)
|
functions.add(name)
|
||||||
|
|
||||||
val outputConfig = data.get("output") { JsonObject() }
|
val outputConfig = data.get("output") { JsonObject() }
|
||||||
val output = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.output)
|
val node = Registries.behaviorNodes.getOrThrow(name).value
|
||||||
|
val output = LinkedHashMap(node.output)
|
||||||
|
|
||||||
|
// original engine doesn't do this
|
||||||
|
if (node.script != null)
|
||||||
|
scripts.add(node.script.fullPath)
|
||||||
|
|
||||||
for ((k, v) in output.entries) {
|
for ((k, v) in output.entries) {
|
||||||
val replaced = replaceOutputBehaviorTag(outputConfig[k]?.asString ?: v.key, treeParameters)
|
val replaced = replaceOutputBehaviorTag(outputConfig[k]?.asString ?: v.key, treeParameters)
|
||||||
@ -154,14 +159,14 @@ private fun createNode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
check(lua.loadGlobal("ActionNode") == LuaType.FUNCTION) { "Global ActionNode is not a Lua function" }
|
val handle = lua.call(1) {
|
||||||
lua.push(name)
|
check(loadGlobal("ActionNode") == LuaType.FUNCTION) { "Global ActionNode is not a Lua function" }
|
||||||
push(lua, parameters)
|
push(name)
|
||||||
push(lua, output)
|
push(this, parameters)
|
||||||
lua.call(3, 1)
|
push(this, output)
|
||||||
val handle = lua.createHandle()
|
}[0]
|
||||||
|
|
||||||
handles.add(handle)
|
handles.add(handle)
|
||||||
lua.pop()
|
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,15 +174,14 @@ private fun createNode(
|
|||||||
functions.add(name)
|
functions.add(name)
|
||||||
val sacrifice = createNode(lua, data["child"] as JsonObject, treeParameters, blackboard, scripts, functions, handles)
|
val sacrifice = createNode(lua, data["child"] as JsonObject, treeParameters, blackboard, scripts, functions, handles)
|
||||||
|
|
||||||
check(lua.loadGlobal("DecoratorNode") == LuaType.FUNCTION) { "Global DecoratorNode is not a Lua function" }
|
val handle = lua.call(1) {
|
||||||
lua.push(name)
|
check(loadGlobal("DecoratorNode") == LuaType.FUNCTION) { "Global DecoratorNode is not a Lua function" }
|
||||||
push(lua, parameters)
|
push(name)
|
||||||
lua.push(sacrifice)
|
push(this, parameters)
|
||||||
lua.call(3, 1)
|
push(sacrifice)
|
||||||
val handle = lua.createHandle()
|
}[0]
|
||||||
handles.add(handle)
|
|
||||||
lua.pop()
|
|
||||||
|
|
||||||
|
handles.add(handle)
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,21 +193,20 @@ private fun createNode(
|
|||||||
|
|
||||||
val factory = CompositeNodeType.entries.valueOf(name)
|
val factory = CompositeNodeType.entries.valueOf(name)
|
||||||
|
|
||||||
check(lua.loadGlobal(factory.fnName) == LuaType.FUNCTION) { "Global ${factory.fnName} is not a Lua function" }
|
val handle = lua.call(1) {
|
||||||
push(lua, parameters)
|
check(loadGlobal(factory.fnName) == LuaType.FUNCTION) { "Global ${factory.fnName} is not a Lua function" }
|
||||||
|
push(this, parameters)
|
||||||
|
|
||||||
lua.pushTable(children.size)
|
pushTable(children.size)
|
||||||
|
|
||||||
for ((i, child) in children.withIndex()) {
|
for ((i, child) in children.withIndex()) {
|
||||||
lua.push(i + 1L)
|
push(i + 1L)
|
||||||
lua.push(child)
|
push(child)
|
||||||
lua.setTableValue()
|
setTableValue()
|
||||||
}
|
}
|
||||||
|
}[0]
|
||||||
|
|
||||||
lua.call(2, 1)
|
|
||||||
val handle = lua.createHandle()
|
|
||||||
handles.add(handle)
|
handles.add(handle)
|
||||||
lua.pop()
|
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,11 +215,7 @@ private fun createNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createBlackboard(lua: LuaThread): LuaHandle {
|
private fun createBlackboard(lua: LuaThread): LuaHandle {
|
||||||
lua.loadGlobal("Blackboard")
|
return lua.call(1) { lua.loadGlobal("Blackboard") }[0]
|
||||||
lua.call(numResults = 1)
|
|
||||||
val handle = lua.createHandle()
|
|
||||||
lua.pop()
|
|
||||||
return handle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
|
private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
|
||||||
@ -269,19 +268,36 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handles.add(blackboard)
|
handles.add(blackboard)
|
||||||
|
|
||||||
args.lua.ensureExtraCapacity(40)
|
args.lua.ensureExtraCapacity(40)
|
||||||
|
|
||||||
|
mergedParams.scripts.forEach { scripts.add(it.fullPath) }
|
||||||
val root = createNode(args.lua, mergedParams.root, mergedParams.mappedParameters, blackboard, scripts, functions, handles)
|
val root = createNode(args.lua, mergedParams.root, mergedParams.mappedParameters, blackboard, scripts, functions, handles)
|
||||||
handles.add(root)
|
handles.add(root)
|
||||||
|
|
||||||
check(args.lua.loadGlobal("BehaviorState") == LuaType.FUNCTION) { "Global BehaviorState is not a Lua function" }
|
scripts.forEach {
|
||||||
|
args.lua.call {
|
||||||
|
loadGlobal("require")
|
||||||
|
push(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
args.lua.push(blackboard)
|
val handle = args.lua.call(1) {
|
||||||
args.lua.push(root)
|
check(loadGlobal("BehaviorState") == LuaType.FUNCTION) { "Global BehaviorState is not a Lua function" }
|
||||||
|
|
||||||
args.lua.call(2, 1)
|
push(blackboard)
|
||||||
|
push(root)
|
||||||
|
}[0]
|
||||||
|
|
||||||
|
args.lua.call {
|
||||||
|
handle.push(this)
|
||||||
|
push("bake")
|
||||||
|
check(loadTableValue() == LuaType.FUNCTION) { "BehaviorTree.bake is not a Lua function" }
|
||||||
|
swap(-2, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.lua.push(handle)
|
||||||
|
|
||||||
|
handle.close()
|
||||||
handles.forEach { it.close() }
|
handles.forEach { it.close() }
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -297,6 +313,7 @@ fun provideBehaviorBindings(lua: LuaThread) {
|
|||||||
|
|
||||||
lua.pop()
|
lua.pop()
|
||||||
|
|
||||||
lua.load(script, "@/internal/behavior.lua")
|
lua.call {
|
||||||
lua.call()
|
load(script, "@/internal/behavior.lua")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,10 +97,12 @@ class LegacyWireProcessor(val world: ServerWorld) {
|
|||||||
if (newState != node.state) {
|
if (newState != node.state) {
|
||||||
try {
|
try {
|
||||||
node.state = newState
|
node.state = newState
|
||||||
entity.lua.pushTable(hashSize = 2)
|
|
||||||
entity.lua.setTableValue("node", i)
|
entity.lua.invokeGlobal("onInputNodeChange") {
|
||||||
entity.lua.setTableValue("level", newState)
|
pushTable(hashSize = 2)
|
||||||
entity.lua.invokeGlobal("onInputNodeChange", 1)
|
setTableValue("node", i)
|
||||||
|
setTableValue("level", newState)
|
||||||
|
}
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err)
|
LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ import ru.dbotthepony.kstarbound.world.api.MutableTileState
|
|||||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.NPCEntity
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -232,7 +233,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
|
|
||||||
for (obj in world.storage.loadEntities(pos).await()) {
|
for (obj in world.storage.loadEntities(pos).await()) {
|
||||||
try {
|
try {
|
||||||
obj.joinWorld(world)
|
if (obj !is NPCEntity)
|
||||||
|
obj.joinWorld(world)
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
LOGGER.error("Exception while spawning entity $obj in world", err)
|
LOGGER.error("Exception while spawning entity $obj in world", err)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
package ru.dbotthepony.kstarbound.server.world
|
package ru.dbotthepony.kstarbound.server.world
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
|
import com.github.luben.zstd.Zstd
|
||||||
|
import com.github.luben.zstd.ZstdDictCompress
|
||||||
|
import com.github.luben.zstd.ZstdDictDecompress
|
||||||
|
import com.github.luben.zstd.ZstdException
|
||||||
|
import com.github.luben.zstd.ZstdInputStreamNoFinalizer
|
||||||
|
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -12,6 +20,7 @@ import kotlinx.coroutines.async
|
|||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.future.asCompletableFuture
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
import kotlinx.coroutines.future.await
|
import kotlinx.coroutines.future.await
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.gson.JsonArrayCollector
|
import ru.dbotthepony.kommons.gson.JsonArrayCollector
|
||||||
import ru.dbotthepony.kommons.gson.contains
|
import ru.dbotthepony.kommons.gson.contains
|
||||||
import ru.dbotthepony.kommons.gson.get
|
import ru.dbotthepony.kommons.gson.get
|
||||||
@ -48,10 +57,12 @@ import ru.dbotthepony.kstarbound.world.UniversePos
|
|||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.Cleaner.Cleanable
|
import java.lang.ref.Cleaner.Cleanable
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
import java.sql.PreparedStatement
|
import java.sql.PreparedStatement
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
|
import java.time.Duration
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -112,9 +123,17 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
`z` INTEGER NOT NULL,
|
`z` INTEGER NOT NULL,
|
||||||
`parameters` BLOB NOT NULL,
|
`parameters` BLOB NOT NULL,
|
||||||
`planets` BLOB NOT NULL,
|
`planets` BLOB NOT NULL,
|
||||||
|
`dictionary` INTEGER NOT NULL,
|
||||||
PRIMARY KEY(`x`, `y`, `z`)
|
PRIMARY KEY(`x`, `y`, `z`)
|
||||||
)
|
)
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
|
|
||||||
|
it.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS `dictionary` (
|
||||||
|
`version` INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
`data` BLOB NOT NULL
|
||||||
|
)
|
||||||
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
database.autoCommit = false
|
database.autoCommit = false
|
||||||
@ -125,10 +144,13 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
private val scope = CoroutineScope(ScheduledCoroutineExecutor(carrier) + SupervisorJob())
|
private val scope = CoroutineScope(ScheduledCoroutineExecutor(carrier) + SupervisorJob())
|
||||||
|
|
||||||
private val selectChunk = database.prepareStatement("SELECT `systems`, `constellations` FROM `chunk` WHERE `x` = ? AND `y` = ?")
|
private val selectChunk = database.prepareStatement("SELECT `systems`, `constellations` FROM `chunk` WHERE `x` = ? AND `y` = ?")
|
||||||
private val selectSystem = database.prepareStatement("SELECT `parameters`, `planets` FROM `system` WHERE `x` = ? AND `y` = ? AND `z` = ?")
|
private val selectSystem = database.prepareStatement("SELECT `parameters`, `planets`, `dictionary` FROM `system` WHERE `x` = ? AND `y` = ? AND `z` = ?")
|
||||||
|
private val selectDictionary = database.prepareStatement("SELECT `data` FROM `dictionary` WHERE `version` = ?")
|
||||||
|
private val selectSamples = database.prepareStatement("SELECT `x`, `y`, `z`, `parameters`, `planets` FROM `system` WHERE `dictionary` = ? ORDER BY RANDOM() LIMIT 2000")
|
||||||
|
|
||||||
private val insertChunk = database.prepareStatement("INSERT INTO `chunk` (`x`, `y`, `systems`, `constellations`) VALUES (?, ?, ?, ?)")
|
private val insertChunk = database.prepareStatement("INSERT INTO `chunk` (`x`, `y`, `systems`, `constellations`) VALUES (?, ?, ?, ?)")
|
||||||
private val insertSystem = database.prepareStatement("INSERT INTO `system` (`x`, `y`, `z`, `parameters`, `planets`) VALUES (?, ?, ?, ?, ?)")
|
private val insertSystem = database.prepareStatement("REPLACE INTO `system` (`x`, `y`, `z`, `parameters`, `planets`, `dictionary`) VALUES (?, ?, ?, ?, ?, ?)")
|
||||||
|
private val insertDictionary = database.prepareStatement("INSERT INTO `dictionary` (`version`, `data`) VALUES (?, ?)")
|
||||||
|
|
||||||
private class SerializedChunk(val x: Int, val y: Int, val systems: ByteArray, val constellations: ByteArray) {
|
private class SerializedChunk(val x: Int, val y: Int, val systems: ByteArray, val constellations: ByteArray) {
|
||||||
fun write(statement: PreparedStatement) {
|
fun write(statement: PreparedStatement) {
|
||||||
@ -164,19 +186,16 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
}.collect(JsonArrayCollector).writeJsonArrayZstd(4)
|
}.collect(JsonArrayCollector).writeJsonArrayZstd(4)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(statement: PreparedStatement) {
|
|
||||||
serialize().write(statement)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SerializedSystem(val x: Int, val y: Int, val z: Int, val parameters: ByteArray, val planets: ByteArray) {
|
private class SerializedSystem(val x: Int, val y: Int, val z: Int, val parameters: ByteArray, val planets: ByteArray, val dictionary: Int) {
|
||||||
fun write(statement: PreparedStatement) {
|
fun write(statement: PreparedStatement) {
|
||||||
statement.setInt(1, x)
|
statement.setInt(1, x)
|
||||||
statement.setInt(2, y)
|
statement.setInt(2, y)
|
||||||
statement.setInt(3, z)
|
statement.setInt(3, z)
|
||||||
statement.setBytes(4, parameters)
|
statement.setBytes(4, parameters)
|
||||||
statement.setBytes(5, planets)
|
statement.setBytes(5, planets)
|
||||||
|
statement.setInt(6, dictionary)
|
||||||
|
|
||||||
statement.execute()
|
statement.execute()
|
||||||
}
|
}
|
||||||
@ -191,19 +210,16 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun serialize(): SerializedSystem {
|
fun serialize(dict: Dicts? = null): SerializedSystem {
|
||||||
return SerializedSystem(
|
return SerializedSystem(
|
||||||
x, y, z,
|
x, y, z,
|
||||||
Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(8),
|
Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(6, dictionary = dict?.compress),
|
||||||
planets.entries.stream()
|
planets.entries.stream()
|
||||||
.map { jsonArrayOf(it.key.first, it.key.second, it.value) }
|
.map { jsonArrayOf(it.key.first, it.key.second, it.value) }
|
||||||
.collect(JsonArrayCollector).writeJsonArrayZstd(8)
|
.collect(JsonArrayCollector).writeJsonArrayZstd(6, dictionary = dict?.compress),
|
||||||
|
dict?.version ?: 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(statement: PreparedStatement) {
|
|
||||||
serialize().write(statement)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// first, chunks in process of loading/generating must not be evicted
|
// first, chunks in process of loading/generating must not be evicted
|
||||||
@ -254,6 +270,239 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
.executor(Starbound.EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<Vector3i, CompletableFuture<System?>>()
|
.build<Vector3i, CompletableFuture<System?>>()
|
||||||
|
|
||||||
|
private class Dicts(val version: Int, bytesParameters: ByteArray) : Closeable {
|
||||||
|
private var compressL: Lazy<ZstdDictCompress>? = lazy(LazyThreadSafetyMode.NONE) { ZstdDictCompress(bytesParameters, 6) }
|
||||||
|
private var decompressL: Lazy<ZstdDictDecompress>? = lazy(LazyThreadSafetyMode.NONE) { ZstdDictDecompress(bytesParameters) }
|
||||||
|
|
||||||
|
val compress get() = compressL!!.value
|
||||||
|
val decompress get() = decompressL!!.value
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
/*if (compressL?.isInitialized() == true) {
|
||||||
|
compressL!!.value.close()
|
||||||
|
compressL = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decompressL?.isInitialized() == true) {
|
||||||
|
decompressL!!.value.close()
|
||||||
|
decompressL = null
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// zstd dicts use finalizers
|
||||||
|
// while we could close the dicts explicitly, they might be still in use
|
||||||
|
// due to off-thread main thread compression being in-process
|
||||||
|
compressL = null
|
||||||
|
decompressL = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dictionaryCache = Caffeine.newBuilder()
|
||||||
|
.maximumSize(32L)
|
||||||
|
.expireAfterAccess(Duration.ofMinutes(5))
|
||||||
|
.executor(Starbound.EXECUTOR)
|
||||||
|
.evictionListener<Int, KOptional<Dicts>> { _, value, _ -> value?.orNull()?.close() }
|
||||||
|
.build<Int, KOptional<Dicts>>()
|
||||||
|
|
||||||
|
private fun loadDictionary(id: Int): Dicts? {
|
||||||
|
return dictionaryCache.get(id) { v ->
|
||||||
|
selectDictionary.setInt(1, v)
|
||||||
|
selectDictionary.executeQuery().use {
|
||||||
|
if (it.next()) {
|
||||||
|
val bytes = decompress(it.getBytes(1), null)
|
||||||
|
return@get KOptional(Dicts(v, bytes))
|
||||||
|
} else {
|
||||||
|
return@get KOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.orNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var latestDictionary = 0
|
||||||
|
private var latestDictionaryCapacity = 0
|
||||||
|
private var dictionary: Dicts? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
database.createStatement().use {
|
||||||
|
it.executeQuery("""
|
||||||
|
SELECT MAX(`version`) FROM `dictionary`
|
||||||
|
""".trimIndent()).use {
|
||||||
|
it.next()
|
||||||
|
// if dictionary table is empty, max() returns null, which gets treated as 0 by getInt
|
||||||
|
latestDictionary = it.getInt(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.executeQuery("""
|
||||||
|
SELECT COUNT(*) FROM `dictionary` WHERE `version` = $latestDictionary
|
||||||
|
""".trimIndent()).use {
|
||||||
|
it.next()
|
||||||
|
latestDictionaryCapacity = it.getInt(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestDictionary != 0) {
|
||||||
|
dictionary = loadDictionary(latestDictionary)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decompress(input: ByteArray, dictionary: ZstdDictDecompress?): ByteArray {
|
||||||
|
val parts = ArrayList<ByteArray>()
|
||||||
|
val stream = ZstdInputStreamNoFinalizer(FastByteArrayInputStream(input))
|
||||||
|
|
||||||
|
if (dictionary != null)
|
||||||
|
stream.setDict(dictionary)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val alloc = ByteArray(1024 * 16)
|
||||||
|
val read = stream.read(alloc)
|
||||||
|
|
||||||
|
if (read <= 0) {
|
||||||
|
break
|
||||||
|
} else if (read == alloc.size) {
|
||||||
|
parts.add(alloc)
|
||||||
|
} else {
|
||||||
|
parts.add(alloc.copyOf(read))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.close()
|
||||||
|
|
||||||
|
val output = ByteArray(parts.sumOf { it.size })
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
for (part in parts) {
|
||||||
|
java.lang.System.arraycopy(part, 0, output, i, part.size)
|
||||||
|
i += part.size
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recompress(input: ByteArray, dict: ZstdDictCompress): ByteArray {
|
||||||
|
val stream = FastByteArrayOutputStream()
|
||||||
|
|
||||||
|
ZstdOutputStreamNoFinalizer(stream, 6).use {
|
||||||
|
it.setDict(dict)
|
||||||
|
it.write(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream.array.copyOf(stream.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recompress(input: ByteArray): ByteArray {
|
||||||
|
val stream = FastByteArrayOutputStream()
|
||||||
|
|
||||||
|
ZstdOutputStreamNoFinalizer(stream, 6).use {
|
||||||
|
it.write(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream.array.copyOf(stream.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dictionaryMaintenance() {
|
||||||
|
val limit = if (latestDictionary == 0) 200 else 2000
|
||||||
|
|
||||||
|
if (latestDictionaryCapacity >= limit) {
|
||||||
|
LOGGER.info("Optimizing star map compression algorithm, star map might become unresponsive for a moment...")
|
||||||
|
|
||||||
|
class Tuple(val x: Int, val y: Int, val z: Int, parameters: ByteArray, planets: ByteArray) {
|
||||||
|
val parameters = Starbound.EXECUTOR.supplyAsync { decompress(parameters, dictionary?.decompress) }
|
||||||
|
val planets = Starbound.EXECUTOR.supplyAsync { decompress(planets, dictionary?.decompress) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// current dictionary is old enough,
|
||||||
|
// create new one to adapt to possible data changes e.g. due to added or removed mods
|
||||||
|
|
||||||
|
// collect samples
|
||||||
|
val samples = ArrayList<Tuple>()
|
||||||
|
|
||||||
|
selectSamples.setInt(1, latestDictionary)
|
||||||
|
selectSamples.executeQuery().use {
|
||||||
|
while (it.next()) {
|
||||||
|
samples.add(Tuple(it.getInt(1), it.getInt(2), it.getInt(3), it.getBytes(4), it.getBytes(5)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for samples to be decompressed
|
||||||
|
val sampleBuffer = ByteBuffer.allocateDirect(samples.sumOf { it.parameters.join().size + it.planets.join().size })
|
||||||
|
|
||||||
|
for (sample in samples) {
|
||||||
|
sampleBuffer.put(sample.parameters.join())
|
||||||
|
sampleBuffer.put(sample.planets.join())
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleBuffer.position(0)
|
||||||
|
|
||||||
|
// create dictionary
|
||||||
|
val buffer = ByteBuffer.allocateDirect(1024 * 1024 * 4) // up to 4 MiB dictionary (before compression, since dedicated zstd dictionaries are not compressed)
|
||||||
|
// 4 MiB seems to be sweet spot, dictionary isn't that big (smaller indices to reference inside dictionary),
|
||||||
|
// takes small amount of space, and training is done moderately fast
|
||||||
|
// Too big dictionaries cause over-fitting and generally *reduce* compression ratio,
|
||||||
|
// while too small dictionaries don't contain enough data to be effective
|
||||||
|
|
||||||
|
val status = Zstd.trainFromBufferDirect(
|
||||||
|
sampleBuffer,
|
||||||
|
IntArray(samples.size) { samples[it].parameters.join().size + samples[it].planets.join().size },
|
||||||
|
buffer,
|
||||||
|
false, 6
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Zstd.isError(status)) {
|
||||||
|
throw ZstdException(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
val copyBytes = ByteArray(status.toInt())
|
||||||
|
buffer.position(0)
|
||||||
|
buffer.get(copyBytes)
|
||||||
|
|
||||||
|
val dicts = Dicts(++latestDictionary, copyBytes)
|
||||||
|
dictionary = dicts
|
||||||
|
dictionaryCache.put(latestDictionary, KOptional(dicts))
|
||||||
|
|
||||||
|
insertDictionary.setInt(1, latestDictionary)
|
||||||
|
insertDictionary.setBytes(2, recompress(copyBytes))
|
||||||
|
insertDictionary.execute()
|
||||||
|
database.commit()
|
||||||
|
|
||||||
|
latestDictionaryCapacity = 0
|
||||||
|
|
||||||
|
LOGGER.info("Star map compression optimized")
|
||||||
|
|
||||||
|
if (latestDictionary == 1) {
|
||||||
|
LOGGER.info("Recompressing star map chunks with new dictionary...")
|
||||||
|
|
||||||
|
// previous data wasn't compressed by any dictionary, so let's recompress it with new dictionary
|
||||||
|
val recompressed = ArrayList<CompletableFuture<Triple<Tuple, ByteArray, ByteArray>>>()
|
||||||
|
|
||||||
|
for (tuple in samples) {
|
||||||
|
recompressed.add(
|
||||||
|
Starbound.EXECUTOR.supplyAsync {
|
||||||
|
Triple(tuple, recompress(tuple.parameters.join(), dicts.compress), recompress(tuple.planets.join(), dicts.compress))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for ((tuple, parameters, planets) in recompressed.map { it.join() }) {
|
||||||
|
insertSystem.setInt(1, tuple.x)
|
||||||
|
insertSystem.setInt(2, tuple.y)
|
||||||
|
insertSystem.setInt(3, tuple.z)
|
||||||
|
insertSystem.setBytes(4, parameters)
|
||||||
|
insertSystem.setBytes(5, planets)
|
||||||
|
insertSystem.setInt(6, 1)
|
||||||
|
insertSystem.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
database.commit()
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
database.rollback()
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Recompressed star map chunks with new dictionary")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadSystem(pos: Vector3i): CompletableFuture<System>? {
|
private fun loadSystem(pos: Vector3i): CompletableFuture<System>? {
|
||||||
selectSystem.setInt(1, pos.x)
|
selectSystem.setInt(1, pos.x)
|
||||||
selectSystem.setInt(2, pos.y)
|
selectSystem.setInt(2, pos.y)
|
||||||
@ -263,12 +512,21 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
if (it.next()) {
|
if (it.next()) {
|
||||||
val parametersBytes = it.getBytes(1)
|
val parametersBytes = it.getBytes(1)
|
||||||
val planetsBytes = it.getBytes(2)
|
val planetsBytes = it.getBytes(2)
|
||||||
|
val dictionaryVersion = it.getInt(3)
|
||||||
|
|
||||||
|
val dict: ZstdDictDecompress?
|
||||||
|
|
||||||
|
if (dictionaryVersion == 0) {
|
||||||
|
dict = null
|
||||||
|
} else {
|
||||||
|
dict = loadDictionary(dictionaryVersion)?.decompress
|
||||||
|
}
|
||||||
|
|
||||||
// deserialize in off-thread since it involves big json structures
|
// deserialize in off-thread since it involves big json structures
|
||||||
Starbound.EXECUTOR.supplyAsync {
|
Starbound.EXECUTOR.supplyAsync {
|
||||||
val parameters: CelestialParameters = Starbound.gson.fromJson(parametersBytes.readJsonElementZstd())!!
|
val parameters: CelestialParameters = Starbound.gson.fromJson(parametersBytes.readJsonElementZstd(dictionary = dict))!!
|
||||||
|
|
||||||
val planets: Map<Pair<Int, Int>, CelestialParameters> = planetsBytes.readJsonArrayZstd().associate {
|
val planets: Map<Pair<Int, Int>, CelestialParameters> = planetsBytes.readJsonArrayZstd(dictionary = dict).associate {
|
||||||
it as JsonArray
|
it as JsonArray
|
||||||
(it[0].asInt to it[1].asInt) to Starbound.gson.fromJson(it[2])!!
|
(it[0].asInt to it[1].asInt) to Starbound.gson.fromJson(it[2])!!
|
||||||
}
|
}
|
||||||
@ -521,6 +779,8 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
val random = random(staticRandom64(chunkPos.x, chunkPos.y, "ChunkIndexMix"))
|
val random = random(staticRandom64(chunkPos.x, chunkPos.y, "ChunkIndexMix"))
|
||||||
val region = chunkRegion(chunkPos)
|
val region = chunkRegion(chunkPos)
|
||||||
|
|
||||||
|
val dict = dictionary
|
||||||
|
|
||||||
return CompletableFuture.supplyAsync(Supplier {
|
return CompletableFuture.supplyAsync(Supplier {
|
||||||
val constellationCandidates = ArrayList<Vector2i>()
|
val constellationCandidates = ArrayList<Vector2i>()
|
||||||
val systemPositions = ArrayList<Vector3i>()
|
val systemPositions = ArrayList<Vector3i>()
|
||||||
@ -534,7 +794,7 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
|
|
||||||
val system = generateSystem(random, pos) ?: continue
|
val system = generateSystem(random, pos) ?: continue
|
||||||
systemPositions.add(pos)
|
systemPositions.add(pos)
|
||||||
systems.add(CompletableFuture.supplyAsync(Supplier { system.serialize() }, Starbound.EXECUTOR))
|
systems.add(CompletableFuture.supplyAsync(Supplier { system.serialize(dict) }, Starbound.EXECUTOR))
|
||||||
systemCache.put(Vector3i(system.x, system.y, system.z), CompletableFuture.completedFuture(system))
|
systemCache.put(Vector3i(system.x, system.y, system.z), CompletableFuture.completedFuture(system))
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -554,6 +814,13 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
serialized.write(insertChunk)
|
serialized.write(insertChunk)
|
||||||
systems.forEach { it.get().write(insertSystem) }
|
systems.forEach { it.get().write(insertSystem) }
|
||||||
database.commit()
|
database.commit()
|
||||||
|
|
||||||
|
if (latestDictionary == (dict?.version ?: 0)) {
|
||||||
|
latestDictionaryCapacity += systems.size
|
||||||
|
// enqueue dictionary maintenance
|
||||||
|
carrier.execute(::dictionaryMaintenance)
|
||||||
|
}
|
||||||
|
|
||||||
chunk
|
chunk
|
||||||
}, carrier)
|
}, carrier)
|
||||||
}, Starbound.EXECUTOR).thenCompose { it }
|
}, Starbound.EXECUTOR).thenCompose { it }
|
||||||
@ -738,4 +1005,8 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val region: AABBi = AABBi(Vector2i(baseInformation.xyCoordRange.x, baseInformation.xyCoordRange.x), Vector2i(baseInformation.xyCoordRange.y, baseInformation.xyCoordRange.y))
|
override val region: AABBi = AABBi(Vector2i(baseInformation.xyCoordRange.x, baseInformation.xyCoordRange.x), Vector2i(baseInformation.xyCoordRange.y, baseInformation.xyCoordRange.y))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,18 +122,16 @@ abstract class SystemWorld(val location: Vector3i, val clock: JVMClock, val univ
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun compatCoordinateSeed(coordinate: UniversePos, seedMix: String): Long {
|
fun compatCoordinateSeed(coordinate: UniversePos, seedMix: String): Long {
|
||||||
// original code is utterly broken here
|
// FIXME: original code is utterly broken here
|
||||||
|
// consider the following:
|
||||||
// consider the following:
|
// auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0;
|
||||||
// auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0;
|
// auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0;
|
||||||
// auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0;
|
// first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0
|
||||||
|
// this "coalesces" planet orbit into either 0 or 1
|
||||||
// first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0
|
// then, we have coordinate.parent().orbitNumber(), which is correct, but only if we are orbiting a satellite
|
||||||
// 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
|
// 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)
|
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?
|
// TODO: hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit maybe?
|
||||||
if (totalDamage > 0.0) {
|
if (totalDamage > 0.0) {
|
||||||
lua.pushTable(hashSize = 4)
|
lua.invokeGlobal("damage") {
|
||||||
lua.setTableValue("sourceId", damage.request.sourceEntityId)
|
pushTable(hashSize = 4)
|
||||||
lua.setTableValue("damage", totalDamage)
|
setTableValue("sourceId", damage.request.sourceEntityId)
|
||||||
lua.setTableValue("sourceDamage", damage.request.damage)
|
setTableValue("damage", totalDamage)
|
||||||
lua.setTableValue("sourceKind", damage.request.damageSourceKind)
|
setTableValue("sourceDamage", damage.request.damage)
|
||||||
|
setTableValue("sourceKind", damage.request.damageSourceKind)
|
||||||
lua.invokeGlobal("damage", 1)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (health <= 0.0) {
|
if (health <= 0.0) {
|
||||||
@ -350,7 +350,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val shouldDie: Boolean get() {
|
private val shouldDie: Boolean get() {
|
||||||
val result = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false)
|
val result = lua.invokeGlobal("shouldDie", 1, {}, { getBoolean() == true }).orElse(false)
|
||||||
return result || health <= 0.0 //|| lua.errorState
|
return result || health <= 0.0 //|| lua.errorState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,13 +332,13 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
|
|||||||
val totalDamage = notifications.sumOf { it.healthLost }
|
val totalDamage = notifications.sumOf { it.healthLost }
|
||||||
|
|
||||||
if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) {
|
if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) {
|
||||||
lua.pushTable(hashSize = 4)
|
lua.invokeGlobal("damage") {
|
||||||
lua.setTableValue("sourceId", damage.request.sourceEntityId)
|
pushTable(hashSize = 4)
|
||||||
lua.setTableValue("damage", totalDamage)
|
setTableValue("sourceId", damage.request.sourceEntityId)
|
||||||
lua.setTableValue("sourceDamage", damage.request.damage)
|
setTableValue("damage", totalDamage)
|
||||||
lua.setTableValue("sourceKind", damage.request.damageSourceKind)
|
setTableValue("sourceDamage", damage.request.damage)
|
||||||
|
setTableValue("sourceKind", damage.request.damageSourceKind)
|
||||||
lua.invokeGlobal("damage", 1)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifications
|
return notifications
|
||||||
@ -407,7 +407,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
|
|||||||
remove(RemovalReason.DYING)
|
remove(RemovalReason.DYING)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
val shouldDie = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false)
|
val shouldDie = lua.invokeGlobal("shouldDie", 1, {}, { getBoolean() == true }).orElse(false)
|
||||||
|
|
||||||
if (shouldDie) {
|
if (shouldDie) {
|
||||||
remove(RemovalReason.DYING)
|
remove(RemovalReason.DYING)
|
||||||
@ -425,7 +425,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
|
|||||||
super.onRemove(world, reason)
|
super.onRemove(world, reason)
|
||||||
|
|
||||||
if (isLocal)
|
if (isLocal)
|
||||||
lua.invokeGlobal("die", 0)
|
lua.invokeGlobal("die")
|
||||||
|
|
||||||
val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() }
|
val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() }
|
||||||
|
|
||||||
|
@ -191,7 +191,6 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
|
|||||||
fun experienceDamage(damage: DamageData): List<DamageNotification> {
|
fun experienceDamage(damage: DamageData): List<DamageNotification> {
|
||||||
val results = lua.invokeGlobal("applyDamageRequest", 1, {
|
val results = lua.invokeGlobal("applyDamageRequest", 1, {
|
||||||
push(Starbound.gson.toJsonTree(damage))
|
push(Starbound.gson.toJsonTree(damage))
|
||||||
1
|
|
||||||
}, { getJson() as? JsonArray }).flatMap { KOptional.ofNullable(it) }
|
}, { getJson() as? JsonArray }).flatMap { KOptional.ofNullable(it) }
|
||||||
|
|
||||||
if (results.isPresent) {
|
if (results.isPresent) {
|
||||||
@ -632,7 +631,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
|
|||||||
|
|
||||||
fun remove() {
|
fun remove() {
|
||||||
if (entity.isLocal)
|
if (entity.isLocal)
|
||||||
lua.invokeGlobal("onExpire", 0)
|
lua.invokeGlobal("onExpire")
|
||||||
|
|
||||||
uniqueEffectMetadata.remove(metadataNetworkID)
|
uniqueEffectMetadata.remove(metadataNetworkID)
|
||||||
|
|
||||||
|
@ -306,13 +306,13 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
fun addConnection(connection: WireConnection) {
|
fun addConnection(connection: WireConnection) {
|
||||||
if (connection !in connectionsInternal) {
|
if (connection !in connectionsInternal) {
|
||||||
connectionsInternal.add(connection.copy())
|
connectionsInternal.add(connection.copy())
|
||||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
lua.invokeGlobal("onNodeConnectionChange")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeConnection(connection: WireConnection) {
|
fun removeConnection(connection: WireConnection) {
|
||||||
if (connectionsInternal.remove(connection)) {
|
if (connectionsInternal.remove(connection)) {
|
||||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
lua.invokeGlobal("onNodeConnectionChange")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,20 +325,20 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index }
|
val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index }
|
||||||
|
|
||||||
if (any == true) {
|
if (any == true) {
|
||||||
otherEntity!!.lua.invokeGlobal("onNodeConnectionChange", 0)
|
otherEntity!!.lua.invokeGlobal("onNodeConnectionChange")
|
||||||
}
|
}
|
||||||
|
|
||||||
any == true
|
any == true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (any)
|
if (any)
|
||||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
lua.invokeGlobal("onNodeConnectionChange")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeConnectionsTo(pos: Vector2i) {
|
fun removeConnectionsTo(pos: Vector2i) {
|
||||||
if (connectionsInternal.removeIf { it.entityLocation == pos }) {
|
if (connectionsInternal.removeIf { it.entityLocation == pos }) {
|
||||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
lua.invokeGlobal("onNodeConnectionChange")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,8 +599,6 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
|
|
||||||
setTableValue("source", diff)
|
setTableValue("source", diff)
|
||||||
setTableValue("sourceId", request.source)
|
setTableValue("sourceId", request.source)
|
||||||
|
|
||||||
1
|
|
||||||
}, { getJson() ?: JsonNull.INSTANCE })
|
}, { getJson() ?: JsonNull.INSTANCE })
|
||||||
|
|
||||||
if (result.isPresent) {
|
if (result.isPresent) {
|
||||||
@ -675,7 +673,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
// break connection if other entity got removed
|
// break connection if other entity got removed
|
||||||
if (connection.otherEntity?.removalReason?.removal == true) {
|
if (connection.otherEntity?.removalReason?.removal == true) {
|
||||||
itr.remove()
|
itr.remove()
|
||||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
lua.invokeGlobal("onNodeConnectionChange")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,7 +689,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
// break connection if we point at invalid node
|
// break connection if we point at invalid node
|
||||||
if (otherNode == null) {
|
if (otherNode == null) {
|
||||||
itr.remove()
|
itr.remove()
|
||||||
lua.invokeGlobal("onNodeConnectionChange", 0)
|
lua.invokeGlobal("onNodeConnectionChange")
|
||||||
} else {
|
} else {
|
||||||
newState = newState!! || otherNode.state
|
newState = newState!! || otherNode.state
|
||||||
}
|
}
|
||||||
@ -706,10 +704,12 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
// otherwise, keep current node state
|
// otherwise, keep current node state
|
||||||
if (newState != null && node.state != newState) {
|
if (newState != null && node.state != newState) {
|
||||||
node.state = newState
|
node.state = newState
|
||||||
lua.pushTable(hashSize = 2)
|
|
||||||
lua.setTableValue("node", i)
|
lua.invokeGlobal("onInputNodeChange") {
|
||||||
lua.setTableValue("level", newState)
|
pushTable(hashSize = 2)
|
||||||
lua.invokeGlobal("onInputNodeChange", 1)
|
setTableValue("node", i)
|
||||||
|
setTableValue("level", newState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -781,9 +781,10 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isRemote && reason.dying) {
|
if (isLocal && reason.dying) {
|
||||||
lua.push(health <= 0.0)
|
lua.invokeGlobal("die") {
|
||||||
lua.invokeGlobal("die", 1)
|
push(health <= 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (doSmash) {
|
if (doSmash) {
|
||||||
|
@ -61,7 +61,7 @@ local function blackboardSet(self, t, key, value)
|
|||||||
local mappings = self.vectorNumberInput[key]
|
local mappings = self.vectorNumberInput[key]
|
||||||
|
|
||||||
if mappings then
|
if mappings then
|
||||||
for _, pair in pairs(input) do
|
for _, pair in pairs(mappings) do
|
||||||
local index = pair[1]
|
local index = pair[1]
|
||||||
local tab = pair[2]
|
local tab = pair[2]
|
||||||
tab[index] = value
|
tab[index] = value
|
||||||
@ -107,12 +107,12 @@ function blackboardPrototype:parameters(parameters, nodeID)
|
|||||||
|
|
||||||
if not typeInput then
|
if not typeInput then
|
||||||
typeInput = {}
|
typeInput = {}
|
||||||
self.input[i][pKey] = typeInput
|
self.input[t][pKey] = typeInput
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(typeInput, {parameterName, tab})
|
table.insert(typeInput, {parameterName, tab})
|
||||||
tab[parameterName] = self.board[t][pKey]
|
tab[parameterName] = self.board[t][pKey]
|
||||||
elseif pValue then
|
elseif pValue ~= nil then
|
||||||
if t == 4 then -- vec2
|
if t == 4 then -- vec2
|
||||||
-- dumb special case for allowing a vec2 of blackboard number keys
|
-- dumb special case for allowing a vec2 of blackboard number keys
|
||||||
if type(pValue) ~= 'table' then
|
if type(pValue) ~= 'table' then
|
||||||
@ -132,7 +132,7 @@ function blackboardPrototype:parameters(parameters, nodeID)
|
|||||||
end
|
end
|
||||||
|
|
||||||
table.insert(typeInput, {i, vector})
|
table.insert(typeInput, {i, vector})
|
||||||
vector[i] = self.board[5][key] -- number
|
vector[i] = self.board[5][vValue] -- number
|
||||||
else
|
else
|
||||||
vector[i] = vValue
|
vector[i] = vValue
|
||||||
end
|
end
|
||||||
@ -142,8 +142,6 @@ function blackboardPrototype:parameters(parameters, nodeID)
|
|||||||
else
|
else
|
||||||
tab[parameterName] = pValue
|
tab[parameterName] = pValue
|
||||||
end
|
end
|
||||||
else
|
|
||||||
error(string.format('parameter %s of type %s for node %s has no key nor value', parameterName, parameter.type, nodeID))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -183,7 +181,7 @@ function blackboardPrototype:clearEphemerals(ephemerals)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function Blackboard()
|
function Blackboard()
|
||||||
return setmetatable({}, blackboardPrototype):ctor()
|
return setmetatable({}, blackboardPrototype):ctor()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -204,6 +202,19 @@ local function runAndReset(self, ...)
|
|||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function reconstructTree(stack)
|
||||||
|
local top = #stack
|
||||||
|
if top == 0 then return '' end
|
||||||
|
|
||||||
|
local result = {'\nbehavior tree traceback:'}
|
||||||
|
|
||||||
|
for i = top, 1, -1 do
|
||||||
|
table.insert(result, string.format('%s%d. - %q', string.rep(' ', top - i + 1), top - i + 1, stack[i]))
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(result, '\n')
|
||||||
|
end
|
||||||
|
|
||||||
-- ActionNode
|
-- ActionNode
|
||||||
|
|
||||||
local actionNode = {}
|
local actionNode = {}
|
||||||
@ -221,10 +232,10 @@ function actionNode:ctor(name, parameters, outputs)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function actionNode:bake()
|
function actionNode:bake()
|
||||||
self.callable = _G[self.name]
|
self.callable = _ENV[self.name]
|
||||||
|
|
||||||
if type(callable) ~= 'function' then
|
if type(self.callable) ~= 'function' then
|
||||||
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable))
|
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -233,7 +244,8 @@ do
|
|||||||
local resume = coroutine.resume
|
local resume = coroutine.resume
|
||||||
local status = coroutine.status
|
local status = coroutine.status
|
||||||
|
|
||||||
function actionNode:run(delta, blackboard)
|
function actionNode:run(delta, blackboard, stack)
|
||||||
|
--table.insert(stack, self.name)
|
||||||
self.calls = self.calls + 1
|
self.calls = self.calls + 1
|
||||||
local status, nodeStatus, nodeExtra
|
local status, nodeStatus, nodeExtra
|
||||||
|
|
||||||
@ -246,11 +258,13 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not status then
|
if not status then
|
||||||
sb.logError('Behavior ActionNode %q failed: %s', self.name, nodeStatus)
|
sb.logError(debug.traceback(self.coroutine, string.format('Behavior ActionNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack))))
|
||||||
|
--table.remove(stack)
|
||||||
return FAILURE
|
return FAILURE
|
||||||
end
|
end
|
||||||
|
|
||||||
if result == nil then
|
if nodeStatus == nil then
|
||||||
|
--table.remove(stack)
|
||||||
return RUNNING
|
return RUNNING
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -258,6 +272,8 @@ do
|
|||||||
blackboard:setOutput(self, nodeExtra)
|
blackboard:setOutput(self, nodeExtra)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--table.remove(stack)
|
||||||
|
|
||||||
if nodeStatus then
|
if nodeStatus then
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
else
|
else
|
||||||
@ -297,10 +313,10 @@ function decoratorNode:ctor(name, parameters, child)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function decoratorNode:bake()
|
function decoratorNode:bake()
|
||||||
self.callable = _G[self.name]
|
self.callable = _ENV[self.name]
|
||||||
|
|
||||||
if type(callable) ~= 'function' then
|
if type(self.callable) ~= 'function' then
|
||||||
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable))
|
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable))
|
||||||
end
|
end
|
||||||
|
|
||||||
self.child:bake()
|
self.child:bake()
|
||||||
@ -311,7 +327,8 @@ do
|
|||||||
local resume = coroutine.resume
|
local resume = coroutine.resume
|
||||||
local coroutine_status = coroutine.status
|
local coroutine_status = coroutine.status
|
||||||
|
|
||||||
function decoratorNode:run(delta, blackboard)
|
function decoratorNode:run(delta, blackboard, stack)
|
||||||
|
--table.insert(stack, self.name)
|
||||||
self.calls = self.calls + 1
|
self.calls = self.calls + 1
|
||||||
|
|
||||||
if not self.coroutine then
|
if not self.coroutine then
|
||||||
@ -320,7 +337,8 @@ do
|
|||||||
local status, nodeStatus = resume(coroutine, parameters, blackboard, self.nodeID, delta)
|
local status, nodeStatus = resume(coroutine, parameters, blackboard, self.nodeID, delta)
|
||||||
|
|
||||||
if not status then
|
if not status then
|
||||||
sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus)
|
sb.logError(debug.traceback(coroutine, string.format('Behavior DecoratorNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack))))
|
||||||
|
--table.remove(stack)
|
||||||
return FAILURE
|
return FAILURE
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -329,38 +347,44 @@ do
|
|||||||
|
|
||||||
if s == 'dead' then
|
if s == 'dead' then
|
||||||
-- quite unexpected, but whatever
|
-- quite unexpected, but whatever
|
||||||
|
--table.remove(stack)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
else
|
else
|
||||||
self.coroutine = coroutine
|
self.coroutine = coroutine
|
||||||
end
|
end
|
||||||
elseif nodeStatus then
|
elseif nodeStatus then
|
||||||
|
--table.remove(stack)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
else
|
else
|
||||||
|
--table.remove(stack)
|
||||||
return FAILURE
|
return FAILURE
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local childStatus = runAndReset(self.child, delta, blackboard)
|
local childStatus = runAndReset(self.child, delta, blackboard, stack)
|
||||||
|
|
||||||
if childStatus == RUNNING then
|
if childStatus == RUNNING then
|
||||||
|
table.remove(stack)
|
||||||
return RUNNING
|
return RUNNING
|
||||||
end
|
end
|
||||||
|
|
||||||
local status, nodeStatus = resume(self.coroutine, childStatus)
|
local status, nodeStatus = resume(self.coroutine, childStatus)
|
||||||
|
|
||||||
if not status then
|
if not status then
|
||||||
sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus)
|
sb.logError(debug.traceback(coroutine, string.format('Behavior DecoratorNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack))))
|
||||||
|
--table.remove(stack)
|
||||||
return FAILURE
|
return FAILURE
|
||||||
end
|
end
|
||||||
|
|
||||||
if nodeStatus == nil then
|
if nodeStatus == nil then
|
||||||
-- another yield OR unexpected return?
|
-- another yield OR unexpected return?
|
||||||
|
|
||||||
local s = coroutine_status(coroutine)
|
local s = coroutine_status(self.coroutine)
|
||||||
|
|
||||||
if s == 'dead' then
|
if s == 'dead' then
|
||||||
self.coroutine = nil
|
self.coroutine = nil
|
||||||
|
--table.remove(stack)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -368,8 +392,10 @@ do
|
|||||||
self.coroutine = nil
|
self.coroutine = nil
|
||||||
|
|
||||||
if nodeStatus then
|
if nodeStatus then
|
||||||
|
--table.remove(stack)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
else
|
else
|
||||||
|
--table.remove(stack)
|
||||||
return FAILURE
|
return FAILURE
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -407,20 +433,29 @@ function seqNode:ctor(children, isSelector)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function seqNode:run(delta, blackboard)
|
function seqNode:run(delta, blackboard, stack)
|
||||||
self.calls = self.calls + 1
|
self.calls = self.calls + 1
|
||||||
local size = self.size
|
local size = self.size
|
||||||
local isSelector = self.isSelector
|
local isSelector = self.isSelector
|
||||||
|
|
||||||
|
--[[if isSelector then
|
||||||
|
table.insert(stack, 'SelectorNode')
|
||||||
|
else
|
||||||
|
table.insert(stack, 'SequenceNode')
|
||||||
|
end]]
|
||||||
|
|
||||||
while self.index <= size do
|
while self.index <= size do
|
||||||
local child = self.children[self.index]
|
local child = self.children[self.index]
|
||||||
local status = runAndReset(child, delta, blackboard)
|
local status = runAndReset(child, delta, blackboard, stack)
|
||||||
|
|
||||||
if status == RUNNING then
|
if status == RUNNING then
|
||||||
|
--table.remove(stack)
|
||||||
return RUNNING
|
return RUNNING
|
||||||
elseif isSelector and status == SUCCESS then
|
elseif isSelector and status == SUCCESS then
|
||||||
|
--table.remove(stack)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
elseif not isSelector and status == FAILURE then
|
elseif not isSelector and status == FAILURE then
|
||||||
|
--table.remove(stack)
|
||||||
return FAILURE
|
return FAILURE
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -464,13 +499,13 @@ parallelNode.__index = parallelNode
|
|||||||
function parallelNode:ctor(parameters, children)
|
function parallelNode:ctor(parameters, children)
|
||||||
self.children = children
|
self.children = children
|
||||||
|
|
||||||
if type(parameters.success) == 'number' then
|
if type(parameters.success) == 'number' and parameters.success >= 0 then
|
||||||
self.successLimit = parameters.success
|
self.successLimit = parameters.success
|
||||||
else
|
else
|
||||||
self.successLimit = #children
|
self.successLimit = #children
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(parameters.fail) == 'number' then
|
if type(parameters.fail) == 'number' and parameters.fail >= 0 then
|
||||||
self.failLimit = parameters.fail
|
self.failLimit = parameters.fail
|
||||||
else
|
else
|
||||||
self.failLimit = #children
|
self.failLimit = #children
|
||||||
@ -483,15 +518,17 @@ function parallelNode:ctor(parameters, children)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function parallelNode:run(delta, blackboard)
|
function parallelNode:run(delta, blackboard, stack)
|
||||||
self.calls = self.calls + 1
|
self.calls = self.calls + 1
|
||||||
local failed = 0
|
local failed = 0
|
||||||
local succeeded = 0
|
local succeeded = 0
|
||||||
local failLimit = self.failLimit
|
local failLimit = self.failLimit
|
||||||
local successLimit = self.successLimit
|
local successLimit = self.successLimit
|
||||||
|
|
||||||
|
--table.insert(stack, 'ParallelNode')
|
||||||
|
|
||||||
for _, node in ipairs(self.children) do
|
for _, node in ipairs(self.children) do
|
||||||
local status = runAndReset(node, delta, blackboard)
|
local status = runAndReset(node, delta, blackboard, stack)
|
||||||
|
|
||||||
if status == SUCCESS then
|
if status == SUCCESS then
|
||||||
succeeded = succeeded + 1
|
succeeded = succeeded + 1
|
||||||
@ -502,16 +539,19 @@ function parallelNode:run(delta, blackboard)
|
|||||||
if failed >= failLimit then
|
if failed >= failLimit then
|
||||||
self.lastFailed = failed
|
self.lastFailed = failed
|
||||||
self.lastSucceed = succeeded
|
self.lastSucceed = succeeded
|
||||||
|
--table.remove(stack)
|
||||||
return FAILURE
|
return FAILURE
|
||||||
elseif succeeded >= successLimit then
|
elseif succeeded >= successLimit then
|
||||||
self.lastFailed = failed
|
self.lastFailed = failed
|
||||||
self.lastSucceed = succeeded
|
self.lastSucceed = succeeded
|
||||||
|
--table.remove(stack)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.lastFailed = failed
|
self.lastFailed = failed
|
||||||
self.lastSucceed = succeeded
|
self.lastSucceed = succeeded
|
||||||
|
--table.remove(stack)
|
||||||
return RUNNING
|
return RUNNING
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -550,11 +590,12 @@ function dynNode:ctor(children)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function dynNode:run(delta, blackboard)
|
function dynNode:run(delta, blackboard, stack)
|
||||||
self.calls = self.calls + 1
|
self.calls = self.calls + 1
|
||||||
|
--table.insert(stack, 'DynamicNode')
|
||||||
|
|
||||||
for i, node in ipairs(self.children) do
|
for i, node in ipairs(self.children) do
|
||||||
local status = runAndReset(node, delta, blackboard)
|
local status = runAndReset(node, delta, blackboard, stack)
|
||||||
|
|
||||||
if stauts == FAILURE and self.index == i then
|
if stauts == FAILURE and self.index == i then
|
||||||
self.index = self.index + 1
|
self.index = self.index + 1
|
||||||
@ -564,10 +605,12 @@ function dynNode:run(delta, blackboard)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if status == SUCCESS or self.index > self.size then
|
if status == SUCCESS or self.index > self.size then
|
||||||
|
--table.remove(stack)
|
||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--table.remove(stack)
|
||||||
return RUNNING
|
return RUNNING
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -605,7 +648,7 @@ function randNode:ctor(children)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function randNode:run(delta, blackboard)
|
function randNode:run(delta, blackboard, stack)
|
||||||
self.calls = self.calls + 1
|
self.calls = self.calls + 1
|
||||||
|
|
||||||
if self.index == -1 and self.size ~= 0 then
|
if self.index == -1 and self.size ~= 0 then
|
||||||
@ -615,7 +658,10 @@ function randNode:run(delta, blackboard)
|
|||||||
if self.index == -1 then
|
if self.index == -1 then
|
||||||
return FAILURE
|
return FAILURE
|
||||||
else
|
else
|
||||||
return runAndReset(self.children[self.index], delta, blackboard)
|
--table.insert(stack, 'RandomNode')
|
||||||
|
local value = runAndReset(self.children[self.index], delta, blackboard, stack)
|
||||||
|
--table.remove(stack)
|
||||||
|
return value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -654,15 +700,20 @@ function statePrototype:ctor(blackboard, root)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function statePrototype:run(delta)
|
function statePrototype:run(delta)
|
||||||
|
local stack = {}
|
||||||
local ephemerals = self._blackboard:takeEphemerals()
|
local ephemerals = self._blackboard:takeEphemerals()
|
||||||
local status = runAndReset(self.root, delta, self._blackboard)
|
local status = runAndReset(self.root, delta, self._blackboard, stack)
|
||||||
self._blackboard:clearEphemerals(ephemerals)
|
self._blackboard:clearEphemerals(ephemerals)
|
||||||
|
|
||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
|
|
||||||
function statePrototype:clear()
|
function statePrototype:clear()
|
||||||
self.tree:reset()
|
self.root:reset()
|
||||||
|
end
|
||||||
|
|
||||||
|
function statePrototype:bake()
|
||||||
|
self.root:bake()
|
||||||
end
|
end
|
||||||
|
|
||||||
function statePrototype:blackboard()
|
function statePrototype:blackboard()
|
||||||
|
@ -385,4 +385,72 @@ function mergeJson(base, with)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local line = ''
|
||||||
|
|
||||||
|
local function puts(f, ...)
|
||||||
|
line = line .. string.format(f, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function flush()
|
||||||
|
if line ~= '' then
|
||||||
|
sb.logInfo(line)
|
||||||
|
line = ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printTable(input, level)
|
||||||
|
level = level or 0
|
||||||
|
|
||||||
|
if not next(input) then
|
||||||
|
puts('{ --[[ empty table ]] }')
|
||||||
|
if level == 0 then flush() end
|
||||||
|
else
|
||||||
|
local prefix = string.rep(' ', level + 1)
|
||||||
|
|
||||||
|
puts('{')
|
||||||
|
flush()
|
||||||
|
|
||||||
|
for k, v in pairs(input) do
|
||||||
|
if type(k) == 'string' then
|
||||||
|
puts('%s[%q] = ', prefix, k)
|
||||||
|
else
|
||||||
|
puts('%s[%s] = ', prefix, k)
|
||||||
|
end
|
||||||
|
|
||||||
|
printValue(v, level + 1)
|
||||||
|
puts(',')
|
||||||
|
flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
puts('%s}', string.rep(' ', level))
|
||||||
|
if level == 0 then flush() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function printValue(input, level)
|
||||||
|
level = level or 0
|
||||||
|
|
||||||
|
local t = type(input)
|
||||||
|
|
||||||
|
if t == 'nil' then
|
||||||
|
puts('%s', 'nil')
|
||||||
|
if level == 0 then flush() end
|
||||||
|
elseif t == 'number' then
|
||||||
|
puts('%f', input)
|
||||||
|
if level == 0 then flush() end
|
||||||
|
elseif t == 'string' then
|
||||||
|
puts('%q', tostring(input))
|
||||||
|
if level == 0 then flush() end
|
||||||
|
elseif t == 'boolean' then
|
||||||
|
puts('%s', tostring(input))
|
||||||
|
if level == 0 then flush() end
|
||||||
|
elseif t == 'table' then
|
||||||
|
printTable(input, level)
|
||||||
|
else
|
||||||
|
puts('unknown value type %q', t)
|
||||||
|
if level == 0 then flush() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ local function entityTypeNamesToIntegers(input, fullName)
|
|||||||
error('invalid entity type ' .. tostring(v) .. ' for ' .. fullName .. ' in types table at index ' .. i, 3)
|
error('invalid entity type ' .. tostring(v) .. ' for ' .. fullName .. ' in types table at index ' .. i, 3)
|
||||||
end
|
end
|
||||||
|
|
||||||
entityTypes[i] = lookup
|
input[i] = lookup
|
||||||
end
|
end
|
||||||
|
|
||||||
return input
|
return input
|
||||||
|
@ -12,12 +12,22 @@ object LuaTests {
|
|||||||
fun test() {
|
fun test() {
|
||||||
val lua = LuaThread()
|
val lua = LuaThread()
|
||||||
|
|
||||||
lua.ensureExtraCapacity(1000)
|
lua.push {
|
||||||
|
throw IllegalArgumentException("This is error message")
|
||||||
|
}
|
||||||
|
|
||||||
lua.loadGlobal("collectgarbage")
|
lua.storeGlobal("test")
|
||||||
lua.push("count")
|
|
||||||
lua.call(1, 1)
|
lua.call {
|
||||||
println(lua.popDouble()!! * 1024)
|
lua.load("""
|
||||||
|
local function errornous()
|
||||||
|
test()
|
||||||
|
end
|
||||||
|
|
||||||
|
local cor = coroutine.create(errornous)
|
||||||
|
print(coroutine.resume(cor))
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
lua.close()
|
lua.close()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user