From 9a958ecccbec4cb639ca91644274d757517e895b Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 28 Dec 2024 21:09:44 +0700 Subject: [PATCH] Make it compile against PUC Lua --- .../ru/dbotthepony/kstarbound/Starbound.kt | 23 - .../kstarbound/defs/item/ItemDescriptor.kt | 93 ---- .../dbotthepony/kstarbound/item/ItemStack.kt | 32 +- .../dbotthepony/kstarbound/lua/Conversions.kt | 445 +----------------- .../dbotthepony/kstarbound/lua/Functions.kt | 400 ---------------- .../kstarbound/lua/LuaEnvironment.kt | 339 ------------- .../lua/LuaMessageHandlerComponent.kt | 17 +- .../kstarbound/lua/LuaSharedState.kt | 17 +- .../dbotthepony/kstarbound/lua/LuaThread.kt | 31 +- .../kstarbound/lua/PrintFunction.kt | 63 --- .../kstarbound/lua/StateMachine.kt | 103 ---- .../bindings/MovementControllerBindings.kt | 4 +- .../kstarbound/lua/bindings/RootBindings.kt | 22 +- .../lua/bindings/ServerWorldBindings.kt | 11 +- .../kstarbound/lua/userdata/BehaviorState.kt | 3 +- .../server/world/LegacyWireProcessor.kt | 9 +- .../kstarbound/server/world/ServerWorld.kt | 2 + .../kstarbound/util/random/RandomUtils.kt | 3 - .../ru/dbotthepony/kstarbound/world/World.kt | 10 + .../world/entities/AbstractEntity.kt | 19 +- .../kstarbound/world/entities/ActorEntity.kt | 54 +-- .../world/entities/DynamicEntity.kt | 4 +- .../world/entities/MonsterEntity.kt | 89 ++-- .../kstarbound/world/entities/NPCEntity.kt | 97 ++-- .../kstarbound/world/entities/PathFinder.kt | 25 +- .../world/entities/StagehandEntity.kt | 61 ++- .../world/entities/StatusController.kt | 237 ++++++---- .../world/entities/api/ScriptedEntity.kt | 20 +- .../world/entities/player/PlayerEntity.kt | 4 +- .../world/entities/tile/WorldObject.kt | 157 +++--- 30 files changed, 530 insertions(+), 1864 deletions(-) delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaEnvironment.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/lua/PrintFunction.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/lua/StateMachine.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 353e198d..bd994fde 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -17,9 +17,6 @@ import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.apache.logging.log4j.LogManager -import org.classdump.luna.compiler.CompilerChunkLoader -import org.classdump.luna.compiler.CompilerSettings -import org.classdump.luna.load.ChunkFactory import ru.dbotthepony.kstarbound.math.AABBTypeAdapter import ru.dbotthepony.kommons.gson.EitherTypeAdapter import ru.dbotthepony.kommons.gson.KOptionalTypeAdapter @@ -281,26 +278,6 @@ object Starbound : BlockableEventLoop("Multiverse Thread"), Scheduler, ISBFileLo } } - private val loader = CompilerChunkLoader.of(CompilerSettings.defaultNoAccountingSettings(), "sb_lua_") - private val scriptCache = ConcurrentHashMap() - - private fun loadScript0(path: String): ChunkFactory { - val find = locate(path) - - if (!find.exists) { - throw NoSuchElementException("Script $path does not exist") - } - - val time = System.nanoTime() - val result = loader.compileTextChunk(path, find.readToString()) - LOGGER.debug("Compiled {} in {} ms", path, (System.nanoTime() - time) / 1_000_000L) - return result - } - - fun loadScript(path: String): ChunkFactory { - return scriptCache.computeIfAbsent(path, ::loadScript0) - } - private val luaScriptCache = Caffeine.newBuilder() .maximumSize(LUA_SCRIPT_CACHE_SIZE) .expireAfterAccess(Duration.ofHours(1L)) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt index 2399efa3..fa54dbe0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt @@ -10,11 +10,6 @@ import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import org.apache.logging.log4j.LogManager -import org.classdump.luna.ByteString -import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.Table -import org.classdump.luna.TableFactory -import org.classdump.luna.runtime.ExecutionContext import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.value @@ -32,16 +27,8 @@ import ru.dbotthepony.kstarbound.item.ItemStack import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement -import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaType -import ru.dbotthepony.kstarbound.lua.StateMachine -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.get -import ru.dbotthepony.kstarbound.lua.indexNoYield -import ru.dbotthepony.kstarbound.lua.toByteString -import ru.dbotthepony.kstarbound.lua.toJson -import ru.dbotthepony.kstarbound.lua.toJsonFromLua import java.io.DataInputStream import java.io.DataOutputStream import java.util.function.Supplier @@ -190,74 +177,6 @@ fun ItemDescriptor(args: LuaThread.ArgStack): ItemDescriptor { return ItemDescriptor(args.lua, args.position) } -fun ItemDescriptor(data: Table, stateMachine: StateMachine): Supplier { - val name = stateMachine.index(data, 1L, "name", "item") - val count = stateMachine.optionalIndex(data, 2L, "count") - val parameters = stateMachine.optionalIndex(data, 3L, "parameters", "data") - var result: KOptional = KOptional.empty() - - stateMachine.add { - val iname = name.get() - val icount = count.get().orElse(1L) - val iparameters = parameters.get().map { if (it is Table) it.toJson(true) else throw LuaRuntimeException("Invalid item descriptor parameters ($it)") }.orElse { JsonObject() } - - if (iname !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${iname})") - if (icount !is Number) throw LuaRuntimeException("Invalid item descriptor count (${icount})") - - result = KOptional(ItemDescriptor(iname.decode(), icount.toLong(), iparameters as JsonObject)) - } - - return Supplier { result.value } -} - -fun ExecutionContext.ItemDescriptor(data: Table): ItemDescriptor { - if (data.metatable?.rawget("__nils") != null) - return ItemDescriptor(toJsonFromLua(data)) // assume it is json - - val name = indexNoYield(data, 1L) ?: indexNoYield(data, "name") ?: indexNoYield(data, "item") - val count = indexNoYield(data, 2L) ?: indexNoYield(data, "count") ?: 1L - val parameters = indexNoYield(data, 3L) ?: indexNoYield(data, "parameters") ?: indexNoYield(data, "data") - - if (name !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${name})") - if (count !is Number) throw LuaRuntimeException("Invalid item descriptor count (${count})") - - if (parameters == null) { - return ItemDescriptor(name.decode(), count.toLong()) - } else if (parameters is Table) { - return ItemDescriptor(name.decode(), count.toLong(), parameters.toJson(true) as JsonObject) - } else { - throw LuaRuntimeException("Invalid item descriptor parameters ($parameters)") - } -} - -fun ExecutionContext.ItemDescriptor(data: Any?): ItemDescriptor { - if (data is ByteString) { - return ItemDescriptor(data.decode(), 1L, JsonObject()) - } else if (data is Table) { - return ItemDescriptor(data) - } else { - return ItemDescriptor.EMPTY - } -} - -@Deprecated("Does not obey meta methods, find replacement where possible") -fun ItemDescriptor(data: Table): ItemDescriptor { - val name = data[1L] ?: data["name"] ?: data["item"] - val count = data[2L] ?: data["count"] ?: 1L - val parameters = data[3L] ?: data["parameters"] ?: data["data"] - - if (name !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${name})") - if (count !is Number) throw LuaRuntimeException("Invalid item descriptor count (${count})") - - if (parameters == null) { - return ItemDescriptor(name.decode(), count.toLong()) - } else if (parameters is Table) { - return ItemDescriptor(name.decode(), count.toLong(), parameters.toJson(true) as JsonObject) - } else { - throw LuaRuntimeException("Invalid item descriptor parameters ($parameters)") - } -} - fun ItemDescriptor(stream: DataInputStream): ItemDescriptor { val name = stream.readInternedString() val count = stream.readVarLong() @@ -324,18 +243,6 @@ data class ItemDescriptor( return !isEmpty } - fun toTable(allocator: TableFactory): Table? { - if (isEmpty) { - return null - } - - return allocator.newTable(0, 3).also { - it.rawset("name", name.toByteString()) - it.rawset("count", count) - it.rawset("parameters", allocator.from(parameters)) - } - } - fun build(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): ItemStack { try { val (jConfig, jParameters) = buildConfig(level, seed, random) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt index 45db946a..b4b80e66 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt @@ -11,13 +11,10 @@ import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import com.google.gson.TypeAdapter import com.google.gson.annotations.JsonAdapter -import com.google.gson.internal.bind.TypeAdapters import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.objects.ObjectArrayList -import org.classdump.luna.Table -import org.classdump.luna.TableFactory import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.get @@ -26,9 +23,7 @@ import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeVarLong import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.util.Either -import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.Globals -import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.Drawable import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect @@ -38,9 +33,8 @@ import ru.dbotthepony.kstarbound.defs.item.ItemRarity import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.stream import ru.dbotthepony.kstarbound.json.writeJsonElement -import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread -import ru.dbotthepony.kstarbound.lua.from +import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.ManualLazy @@ -236,17 +230,17 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para data class AgingResult(val new: ItemStack?, val ageUpdated: Boolean) - private val agingScripts: LuaEnvironment? by lazy { - //val config = config.value ?: return@lazy null - //if (config.itemTags) - null - } - open fun advanceAge(by: Double): AgingResult { - val agingScripts = agingScripts ?: return AgingResult(null, false) + // TODO + val agingScripts = null as LuaThread? ?: return AgingResult(null, false) val descriptor = createDescriptor() - val updated = ItemDescriptor(agingScripts.invokeGlobal("ageItem", descriptor.toTable(agingScripts), by)[0] as Table) + + val updated = agingScripts.invokeGlobal( + "ageItem", 1, + { descriptor.store(this); push(by); 2 }, + ::ItemDescriptor + ).orThrow { IllegalStateException("Aging script did not return new item state") } if (descriptor != updated) { if (descriptor.name == updated.name) { @@ -374,14 +368,6 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para return createDescriptor().toJson() } - fun toTable(allocator: TableFactory): Table? { - if (isEmpty) { - return null - } - - return createDescriptor().toTable(allocator) - } - class Adapter(gson: Gson) : TypeAdapter() { override fun write(out: JsonWriter, value: ItemStack?) { val json = value?.toJson() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt index b42451a8..c2b184a0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt @@ -1,22 +1,7 @@ package ru.dbotthepony.kstarbound.lua -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap -import org.classdump.luna.ByteString -import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.Table -import org.classdump.luna.TableFactory -import org.classdump.luna.impl.NonsuspendableFunctionException -import org.classdump.luna.runtime.AbstractFunction3 -import org.classdump.luna.runtime.ExecutionContext -import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.util.Either -import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2f import ru.dbotthepony.kommons.util.IStruct2i @@ -27,438 +12,14 @@ import ru.dbotthepony.kommons.util.IStruct4d import ru.dbotthepony.kommons.util.IStruct4f import ru.dbotthepony.kommons.util.IStruct4i import ru.dbotthepony.kstarbound.Registry +import ru.dbotthepony.kstarbound.math.AABB +import ru.dbotthepony.kstarbound.math.AABBi +import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.math.vector.Vector2i -import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter -import ru.dbotthepony.kstarbound.math.AABBi -import ru.dbotthepony.kstarbound.math.Line2d -import ru.dbotthepony.kstarbound.util.sbIntern import ru.dbotthepony.kstarbound.world.physics.Poly -fun ExecutionContext.toVector2i(table: Any): Vector2i { - val x = indexNoYield(table, 1L) - val y = indexNoYield(table, 2L) - returnBuffer.setTo() - - if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") - if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") - - return Vector2i(x.toInt(), y.toInt()) -} - -fun ExecutionContext.toVector2d(table: Any): Vector2d { - val x = indexNoYield(table, 1L) - val y = indexNoYield(table, 2L) - returnBuffer.setTo() - - if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") - if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") - - return Vector2d(x.toDouble(), y.toDouble()) -} - -fun ExecutionContext.toLine2d(table: Any): Line2d { - val p0 = toVector2d(indexNoYield(table, 1L) ?: throw LuaRuntimeException("Invalid line: $table")) - val p1 = toVector2d(indexNoYield(table, 2L) ?: throw LuaRuntimeException("Invalid line: $table")) - return Line2d(p0, p1) -} - -fun String?.toByteString(): ByteString? { - return if (this == null) null else ByteString.of(this) -} - -fun ExecutionContext.toPoly(table: Table): Poly { - val vertices = ArrayList() - - for ((_, v) in table) { - vertices.add(toVector2d(v)) - } - - return Poly(vertices) -} - -fun ExecutionContext.toVector2f(table: Any): Vector2f { - val x = indexNoYield(table, 1L) - val y = indexNoYield(table, 2L) - returnBuffer.setTo() - - if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") - if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") - - return Vector2f(x.toFloat(), y.toFloat()) -} - -fun ExecutionContext.toColor(table: Any): RGBAColor { - val x = indexNoYield(table, 1L) - val y = indexNoYield(table, 2L) - val z = indexNoYield(table, 3L) - val w = indexNoYield(table, 4L) ?: 255 - returnBuffer.setTo() - - if (x !is Number) throw ClassCastException("Expected table representing a Color, but value at [1] is not a number: $x") - if (y !is Number) throw ClassCastException("Expected table representing a Color, but value at [2] is not a number: $y") - if (z !is Number) throw ClassCastException("Expected table representing a Color, but value at [3] is not a number: $z") - if (w !is Number) throw ClassCastException("Expected table representing a Color, but value at [4] is not a number: $w") - - return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) -} - -fun ExecutionContext.toAABB(table: Any): AABB { - val x = indexNoYield(table, 1L) - val y = indexNoYield(table, 2L) - val z = indexNoYield(table, 3L) - val w = indexNoYield(table, 4L) - returnBuffer.setTo() - - if (x !is Number) throw ClassCastException("Expected table representing a AABB, but value at [1] is not a number: $x") - if (y !is Number) throw ClassCastException("Expected table representing a AABB, but value at [2] is not a number: $y") - if (z !is Number) throw ClassCastException("Expected table representing a AABB, but value at [3] is not a number: $z") - if (w !is Number) throw ClassCastException("Expected table representing a AABB, but value at [4] is not a number: $w") - - return AABB(Vector2d(x.toDouble(), y.toDouble()), Vector2d(z.toDouble(), w.toDouble())) -} - -fun ExecutionContext.toAABBi(table: Any): AABBi { - val x = indexNoYield(table, 1L) - val y = indexNoYield(table, 2L) - val z = indexNoYield(table, 3L) - val w = indexNoYield(table, 4L) - returnBuffer.setTo() - - if (x !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [1] is not a number: $x") - if (y !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [2] is not a number: $y") - if (z !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [3] is not a number: $z") - if (w !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [4] is not a number: $w") - - return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt())) -} - -fun toJsonFromLua(value: Any?): JsonElement { - return when (value) { - null, is JsonNull -> JsonNull.INSTANCE - is String -> JsonPrimitive(value.sbIntern()) - is ByteString -> JsonPrimitive(value.decode().sbIntern()) - is Number -> JsonPrimitive(value) - is Boolean -> InternedJsonElementAdapter.of(value) - is Table -> value.toJson() - else -> throw IllegalArgumentException("Unable to translate $value into json!") - } -} - -fun Table.toJson(forceObject: Boolean = false): JsonElement { - val arrayValues = Long2ObjectAVLTreeMap() - val hashValues = HashMap() - - val meta = metatable - var hint = LUA_HINT_NONE - - if (meta != null) { - val getHint = meta["__typehint"] - - if (getHint is Number) { - hint = getHint.toLong() - } - - val nils = meta["__nils"] - - if (nils is Table) { - // Nil entries just have a garbage integer as their value - for ((k, v) in nils) { - val ik = k.toLuaInteger() - - if (ik != null) { - arrayValues[ik] = JsonNull.INSTANCE - } else { - hashValues[k.toString()] = JsonNull.INSTANCE - } - } - } - } - - for ((k, v) in this) { - val ik = k.toLuaInteger() - - if (ik != null) { - arrayValues[ik] = toJsonFromLua(v) - } else { - hashValues[k.toString()] = toJsonFromLua(v) - } - } - - val interpretAsList = !forceObject && hashValues.isEmpty() && hint != LUA_HINT_OBJECT - - if (interpretAsList) { - val list = JsonArray() - - for ((k, v) in arrayValues) { - val ik = k.toInt() - 1 - - while (list.size() <= ik) { - list.add(JsonNull.INSTANCE) - } - - list[ik] = v - } - - return list - } else { - for ((k, v) in arrayValues) { - hashValues[(k - 1L).toString()] = v - } - - return JsonObject().apply { - for ((k, v) in hashValues) { - this[k] = v - } - } - } -} - -fun TableFactory.from(value: JsonElement?): Any? { - when (value) { - is JsonPrimitive -> { - if (value.isNumber) { - return when (val v = value.asNumber) { - is Float, is Double -> value.asDouble - is Int, is Long -> value.asLong - - else -> { - // hi com.google.gson.internal.LazilyParsedNumber - val doubleBits by lazy { v.toDouble() } - - if (v.toString().contains('.') || doubleBits % 1.0 != 0.0) { - v.toDouble() - } else { - v.toLong() - } - } - } - } else if (value.isString) { - return ByteString.of(value.asString) - } else if (value.isBoolean) { - return value.asBoolean - } else { - throw RuntimeException("unreachable code") - } - } - - is JsonArray -> return from(value) - is JsonObject -> return from(value) - null, is JsonNull -> return null - else -> throw RuntimeException(value::class.qualifiedName) - } -} - -private data class JsonTable(val metatable: Table, val nils: Table, val data: Table) - -fun Any?.toLuaInteger(): Long? { - if (this == null) - return null - else if (this is Long) - return this - else if (this is Double) { - if (this % 1.0 == 0.0) { - return this.toLong() - } else { - return null - } - } else if (this is ByteString) { - val decoded = decode() - - if (decoded.contains('.')) - return null - - return decoded.toLongOrNull() - } else { - return null - } -} - -const val LUA_HINT_NONE = 0L -const val LUA_HINT_ARRAY = 1L -const val LUA_HINT_OBJECT = 2L - -private object JsonTableIndex : AbstractFunction3() { - override fun resume(context: ExecutionContext?, suspendedState: Any?) { - throw NonsuspendableFunctionException(this::class.java) - } - - private val __nils: ByteString = ByteString.of("__nils") - - override fun invoke(context: ExecutionContext, self: Table, key: Any, value: Any?) { - val meta = self.metatable - val nils = meta[__nils] as Table - - // If we are setting an entry to nil, need to add a bogus integer entry - // to the __nils table, otherwise need to set the entry *in* the __nils - // table to nil and remove it. - - // TODO: __newindex is called only when assigning non-existing keys to values, - // TODO: as per Lua manual. - // TODO: Chucklefish weren't aware of this? - - if (value == null) { - nils[key] = 0L - } else { - nils[key] = null as Any? - } - - self[key] = value - } -} - -private fun TableFactory.createJsonTable(typeHint: Long, size: Int, hash: Int): JsonTable { - val metatable = newTable() - val nils = newTable() - val data = newTable(size, hash) - - metatable["__newindex"] = JsonTableIndex - metatable["__nils"] = nils - metatable["__typehint"] = typeHint - - data.metatable = metatable - return JsonTable(metatable, nils, data) -} - -fun TableFactory.from(value: JsonObject): Table { - val (_, nils, data) = createJsonTable(LUA_HINT_OBJECT, 0, value.size()) - - for ((k, v) in value.entrySet()) { - if (v.isJsonNull) { - nils[k] = 0L - } else { - data[k] = from(v) - } - } - - return data -} - -fun TableFactory.from(value: Int?): Long? { - return value?.toLong() -} - -fun TableFactory.from(value: Long?): Long? { - return value -} - -fun TableFactory.from(value: String?): ByteString? { - return value.toByteString() -} - -fun TableFactory.from(value: ByteString?): ByteString? { - return value -} - -fun TableFactory.from(value: JsonArray): Table { - val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size()) - - for ((i, v) in value.withIndex()) { - if (v.isJsonNull) { - nils[i + 1L] = 0L - } else { - data[i + 1L] = from(v) - } - } - - return data -} - -fun TableFactory.createJsonObject(): Table { - return createJsonTable(LUA_HINT_OBJECT, 0, 0).data -} - -fun TableFactory.createJsonArray(): Table { - return createJsonTable(LUA_HINT_ARRAY, 0, 0).data -} - -fun TableFactory.from(value: IStruct2d?): Table? { - value ?: return null - - return newTable(2, 0).apply { - this[1L] = value.component1() - this[2L] = value.component2() - } -} - -fun TableFactory.from(value: Poly?): Table? { - value ?: return null - - return newTable(value.vertices.size, 0).apply { - value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) } - } -} - -fun TableFactory.from(value: IStruct2i?): Table? { - value ?: return null - - return newTable(2, 0).also { - it.rawset(1L, value.component1()) - it.rawset(2L, value.component2()) - } -} - -fun TableFactory.from(value: IStruct3i?): Table? { - value ?: return null - - return newTable(3, 0).also { - it.rawset(1L, value.component1()) - it.rawset(2L, value.component2()) - it.rawset(3L, value.component3()) - } -} - -fun TableFactory.from(value: IStruct4i?): Table? { - value ?: return null - - return newTable(3, 0).also { - it.rawset(1L, value.component1()) - it.rawset(2L, value.component2()) - it.rawset(3L, value.component3()) - it.rawset(4L, value.component4()) - } -} - -fun TableFactory.from(value: RGBAColor?): Table? { - value ?: return null - - return newTable(3, 0).also { - it.rawset(1L, value.redInt.toLong()) - it.rawset(2L, value.greenInt.toLong()) - it.rawset(3L, value.blueInt.toLong()) - it.rawset(4L, value.alphaInt.toLong()) - } -} - -fun TableFactory.from(value: Collection): Table { - return newTable(value.size, 0).also { - for ((i, v) in value.withIndex()) { - it.rawset(i + 1L, v) - } - } -} - -fun TableFactory.from(value: AABB?): Table? { - value ?: return null - - return newTable(3, 0).also { - it.rawset(1L, value.mins.x) - it.rawset(2L, value.mins.y) - it.rawset(3L, value.maxs.x) - it.rawset(4L, value.maxs.y) - } -} - -fun TableFactory.tableFrom(collection: Collection): Table { - val alloc = newTable(collection.size, 0) - - for ((i, v) in collection.withIndex()) - alloc[i + 1L] = v - - return alloc -} - // TODO: error reporting when argument was provided, but it is malformed // currently, invalid data gets silently discarded, and treated as "no value" aka null fun LuaThread.getPoly(stackIndex: Int = -1): Poly? { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt deleted file mode 100644 index 0c934f13..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Functions.kt +++ /dev/null @@ -1,400 +0,0 @@ -package ru.dbotthepony.kstarbound.lua - -import com.google.gson.JsonElement -import it.unimi.dsi.fastutil.objects.ObjectIterators -import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.Table -import org.classdump.luna.TableFactory -import org.classdump.luna.impl.NonsuspendableFunctionException -import org.classdump.luna.lib.ArgumentIterator -import org.classdump.luna.lib.TableLib -import org.classdump.luna.runtime.AbstractFunction0 -import org.classdump.luna.runtime.AbstractFunction1 -import org.classdump.luna.runtime.AbstractFunction2 -import org.classdump.luna.runtime.AbstractFunction3 -import org.classdump.luna.runtime.AbstractFunction4 -import org.classdump.luna.runtime.AbstractFunctionAnyArg -import org.classdump.luna.runtime.Dispatch -import org.classdump.luna.runtime.ExecutionContext -import org.classdump.luna.runtime.LuaFunction -import org.classdump.luna.runtime.UnresolvedControlThrowable -import java.util.Spliterator -import java.util.Spliterators -import java.util.stream.Stream -import java.util.stream.StreamSupport -import kotlin.math.max -import kotlin.math.min - -fun ExecutionContext.indexNoYield(table: Any, key: Any): Any? { - return try { - Dispatch.index(this, table, key) - returnBuffer.get0() - } catch (err: UnresolvedControlThrowable) { - throw RuntimeException("attempt to yield across C boundary", err) - } -} - -fun ExecutionContext.indexSetNoYield(table: Any, key: Any, value: Any?) { - try { - Dispatch.setindex(this, table, key, value) - } catch (err: UnresolvedControlThrowable) { - throw RuntimeException("attempt to yield across C boundary", err) - } -} - -fun ArgumentIterator.nextOptionalFloat(): Double? { - if (hasNext()) { - return nextFloat() - } else { - return null - } -} - -fun ArgumentIterator.nextOptionalInteger(): Long? { - if (hasNext()) { - return nextInteger() - } else { - return null - } -} - -operator fun Table.set(index: Any, value: Any?) { - rawset(index, value) -} - -@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR) -operator fun Table.set(index: Any, value: JsonElement?) { - rawset(index, value) -} - -operator fun Table.set(index: Long, value: Any?) { - rawset(index, value) -} - -@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR) -operator fun Table.set(index: Long, value: JsonElement?) { - rawset(index, value) -} - -operator fun Table.set(index: Int, value: Any?) { - rawset(index.toLong(), value) -} - -@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR) -operator fun Table.set(index: Int, value: JsonElement?) { - rawset(index.toLong(), value) -} - -operator fun Table.get(index: Any): Any? = rawget(index) -operator fun Table.get(index: Long): Any? = rawget(index) -operator fun Table.get(index: Int): Any? = rawget(index.toLong()) - -operator fun Table.contains(index: Any): Boolean { - return rawget(index) != null -} - -operator fun Table.contains(index: Long): Boolean { - return rawget(index) != null -} - -operator fun Table.iterator(): Iterator> { - var key: Any? = initialKey() ?: return ObjectIterators.emptyIterator() - data class Pair(override val key: Any, override val value: Any) : Map.Entry - - return object : Iterator> { - override fun hasNext(): Boolean { - return key != null - } - - override fun next(): Map.Entry { - val ikey = key ?: throw NoSuchElementException() - val value = get(ikey)!! - key = successorKeyOf(ikey) - return Pair(ikey, value) - } - } -} - -fun Table.spliterator(): Spliterator> { - return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED) -} - -fun Table.stream(): Stream> { - return StreamSupport.stream(spliterator(), false) -} - -/** - * to be used in places where we need to "unpack" table, like this: - * - * ```lua - * local array = unpack(tab) - * ``` - * - * except this function unpacks using rawget - */ -fun Table.unpackAsArray(): Array { - var min = Long.MAX_VALUE - var max = 0L - - for ((k, v) in this) { - if (k is Long) { - max = max(k, max) - min = min(k, min) - } - } - - val length = max - min - - if (length <= 0L) - return arrayOf() - - val array = arrayOfNulls(length.toInt()) - var i2 = 0 - - for (i in min .. max) { - array[i2++] = this[i] - } - - return array -} - -fun TableFactory.tableOf(vararg values: Any?): Table { - val table = newTable(values.size, 0) - - for ((i, v) in values.withIndex()) { - table[i + 1L] = v - } - - return table -} - -fun TableFactory.tableOf(vararg values: Int): Table { - val table = newTable(values.size, 0) - - for ((i, v) in values.withIndex()) { - table[i + 1L] = v.toLong() - } - - return table -} - -fun TableFactory.tableOf(vararg values: Long): Table { - val table = newTable(values.size, 0) - - for ((i, v) in values.withIndex()) { - table[i + 1L] = v - } - - return table -} - -fun TableFactory.tableMapOf(vararg values: Pair): Table { - val table = newTable(0, values.size) - - for ((k, v) in values) { - table[k] = v - } - - return table -} - -fun TableFactory.tableOf(): Table { - return newTable() -} - -@Deprecated("Function is a stub") -fun luaStub(message: String = "not yet implemented"): LuaFunction { - return object : LuaFunction() { - override fun resume(context: ExecutionContext?, suspendedState: Any?) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext?) { - throw LuaRuntimeException("NYI: $message") - } - - override fun invoke(context: ExecutionContext?, arg1: Any?) { - throw LuaRuntimeException("NYI: $message") - } - - override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?) { - throw LuaRuntimeException("NYI: $message") - } - - override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?) { - throw LuaRuntimeException("NYI: $message") - } - - override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?) { - throw LuaRuntimeException("NYI: $message") - } - - override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?, arg5: Any?) { - throw LuaRuntimeException("NYI: $message") - } - - override fun invoke(context: ExecutionContext?, args: Array?) { - throw LuaRuntimeException("NYI: $message") - } - } -} - -fun luaFunctionN(name: String, callable: ExecutionContext.(ArgumentIterator) -> Unit): LuaFunction { - return object : AbstractFunctionAnyArg() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext, args: Array) { - context.returnBuffer.setTo() - - try { - callable.invoke(context, ArgumentIterator.of(context, name, args)) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} - -fun luaFunctionNS(name: String, callable: ExecutionContext.(ArgumentIterator) -> StateMachine): LuaFunction { - return object : AbstractFunctionAnyArg() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - (suspendedState as StateMachine).run(context) - } - - override fun invoke(context: ExecutionContext, args: Array) { - context.returnBuffer.setTo() - - try { - callable.invoke(context, ArgumentIterator.of(context, name, args)).run(context) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} - -fun luaFunctionArray(callable: ExecutionContext.(Array) -> Unit): LuaFunction { - return object : AbstractFunctionAnyArg() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext, args: Array) { - context.returnBuffer.setTo() - - try { - callable.invoke(context, args) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} - -fun luaFunction(callable: ExecutionContext.() -> Unit): LuaFunction<*, *, *, *, *> { - return object : AbstractFunction0() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext) { - context.returnBuffer.setTo() - - try { - callable.invoke(context) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} - -fun luaFunction(callable: ExecutionContext.(T) -> Unit): LuaFunction { - return object : AbstractFunction1() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext, arg1: T) { - context.returnBuffer.setTo() - - try { - callable.invoke(context, arg1) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} - -fun luaFunction(callable: ExecutionContext.(T, T2) -> Unit): LuaFunction { - return object : AbstractFunction2() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext, arg1: T, arg2: T2) { - context.returnBuffer.setTo() - - try { - callable.invoke(context, arg1, arg2) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} - -fun luaFunction(callable: ExecutionContext.(T, T2, T3) -> Unit): LuaFunction { - return object : AbstractFunction3() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3) { - context.returnBuffer.setTo() - - try { - callable.invoke(context, arg1, arg2, arg3) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} - -fun luaFunction(callable: ExecutionContext.(T, T2, T3, T4) -> Unit): LuaFunction { - return object : AbstractFunction4() { - override fun resume(context: ExecutionContext, suspendedState: Any) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3, arg4: T4) { - context.returnBuffer.setTo() - - try { - callable.invoke(context, arg1, arg2, arg3, arg4) - } catch (err: ClassCastException) { - throw LuaRuntimeException(err) - } catch (err: NullPointerException) { - throw LuaRuntimeException(err) - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaEnvironment.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaEnvironment.kt deleted file mode 100644 index 71f85ac6..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaEnvironment.kt +++ /dev/null @@ -1,339 +0,0 @@ -package ru.dbotthepony.kstarbound.lua - -import it.unimi.dsi.fastutil.objects.ObjectArraySet -import org.apache.logging.log4j.LogManager -import org.classdump.luna.ByteString -import org.classdump.luna.LuaObject -import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.LuaType -import org.classdump.luna.StateContext -import org.classdump.luna.Table -import org.classdump.luna.Variable -import org.classdump.luna.compiler.CompilerChunkLoader -import org.classdump.luna.compiler.CompilerSettings -import org.classdump.luna.env.RuntimeEnvironments -import org.classdump.luna.exec.CallPausedException -import org.classdump.luna.exec.Continuation -import org.classdump.luna.exec.DirectCallExecutor -import org.classdump.luna.impl.DefaultTable -import org.classdump.luna.impl.NonsuspendableFunctionException -import org.classdump.luna.lib.BasicLib -import org.classdump.luna.lib.CoroutineLib -import org.classdump.luna.lib.MathLib -import org.classdump.luna.lib.OsLib -import org.classdump.luna.lib.StringLib -import org.classdump.luna.lib.TableLib -import org.classdump.luna.lib.Utf8Lib -import org.classdump.luna.load.ChunkFactory -import org.classdump.luna.runtime.AbstractFunction1 -import org.classdump.luna.runtime.Coroutine -import org.classdump.luna.runtime.ExecutionContext -import org.classdump.luna.runtime.LuaFunction -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.AssetPath -import ru.dbotthepony.kstarbound.lua.bindings.provideRootBindings -import ru.dbotthepony.kstarbound.lua.bindings.provideUtilityBindings -import ru.dbotthepony.kstarbound.util.random.random -import java.util.concurrent.atomic.AtomicLong - -class LuaEnvironment : StateContext { - private var nilMeta: Table? = null - private var booleanMeta: Table? = null - private var numberMeta: Table? = null - private var stringMeta: Table? = null - private var functionMeta: Table? = null - private var threadMeta: Table? = null - private var lightUserdataMeta: Table? = null - - override fun getNilMetatable(): Table? { - return nilMeta - } - - override fun getBooleanMetatable(): Table? { - return booleanMeta - } - - override fun getNumberMetatable(): Table? { - return numberMeta - } - - override fun getStringMetatable(): Table? { - return stringMeta - } - - override fun getFunctionMetatable(): Table? { - return functionMeta - } - - override fun getThreadMetatable(): Table? { - return threadMeta - } - - override fun getLightUserdataMetatable(): Table? { - return lightUserdataMeta - } - - override fun getMetatable(instance: Any?): Table? { - if (instance is LuaObject) - return instance.metatable - - return when (val type = LuaType.typeOf(instance)!!) { - LuaType.NIL -> nilMeta - LuaType.BOOLEAN -> booleanMeta - LuaType.NUMBER -> numberMeta - LuaType.STRING -> stringMeta - LuaType.FUNCTION -> functionMeta - LuaType.USERDATA -> lightUserdataMeta - LuaType.THREAD -> threadMeta - else -> throw IllegalArgumentException("Illegal type: $type") - } - } - - override fun setNilMetatable(table: Table?): Table? { - val old = nilMeta; nilMeta = table; return old - } - - override fun setBooleanMetatable(table: Table?): Table? { - val old = booleanMeta; booleanMeta = table; return old - } - - override fun setNumberMetatable(table: Table?): Table? { - val old = numberMeta; numberMeta = table; return old - } - - override fun setStringMetatable(table: Table?): Table? { - val old = stringMeta; stringMeta = table; return old - } - - override fun setFunctionMetatable(table: Table?): Table? { - val old = functionMeta; functionMeta = table; return old - } - - override fun setThreadMetatable(table: Table?): Table? { - val old = threadMeta; threadMeta = table; return old - } - - override fun setLightUserdataMetatable(table: Table?): Table? { - val old = lightUserdataMeta; lightUserdataMeta = table; return old - } - - override fun setMetatable(instance: Any?, table: Table?): Table? { - if (instance is LuaObject) - return instance.setMetatable(table) - else - throw IllegalArgumentException("Can not set metatable of ${LuaType.typeOf(instance)}") - } - - override fun newTable(): Table { - return DefaultTable() - } - - override fun newTable(array: Int, hash: Int): Table { - return DefaultTable() - } - - val globals: Table = newTable() - val executor: DirectCallExecutor = DirectCallExecutor.newExecutor() - var random = random() - - init { - globals["_G"] = globals - - globals["assert"] = BasicLib.assertFn() - globals["error"] = BasicLib.error() - globals["getmetatable"] = BasicLib.getmetatable() - globals["ipairs"] = BasicLib.ipairs() - globals["next"] = BasicLib.next() - globals["pairs"] = BasicLib.pairs() - globals["pcall"] = BasicLib.pcall() - - globals["rawequal"] = BasicLib.rawequal() - globals["rawget"] = BasicLib.rawget() - globals["rawlen"] = BasicLib.rawlen() - globals["rawset"] = BasicLib.rawset() - globals["select"] = BasicLib.select() - globals["setmetatable"] = BasicLib.setmetatable() - globals["tostring"] = BasicLib.tostring() - globals["tonumber"] = BasicLib.tonumber() - globals["type"] = BasicLib.type() - globals["_VERSION"] = BasicLib._VERSION - globals["xpcall"] = BasicLib.xpcall() - - globals["print"] = PrintFunction(globals) - - // why not use _ENV anyway lol - globals["self"] = newTable() - - CoroutineLib.installInto(this, globals) - TableLib.installInto(this, globals) - MathLib.installInto(this, globals) - StringLib.installInto(this, globals) - OsLib.installInto(this, globals, RuntimeEnvironments.system()) - - val math = globals["math"] as Table - - math["random"] = luaFunction { origin: Number?, bound: Number? -> - if (origin == null && bound == null) { - returnBuffer.setTo(random.nextDouble()) - } else if (bound == null) { - val origin = origin!!.toLong() - - if (origin > 1L) { - returnBuffer.setTo(random.nextLong(1L, origin)) - } else if (origin == 1L) { - returnBuffer.setTo(1L) - } else { - throw LuaRuntimeException("bad argument #1 to 'random' (interval is empty)") - } - } else { - val origin = origin!!.toLong() - val bound = bound.toLong() - - if (bound > origin) { - returnBuffer.setTo(random.nextLong(origin, bound)) - } else if (bound == origin) { - returnBuffer.setTo(origin) - } else { - throw LuaRuntimeException("bad argument #1 to 'random' (interval is empty)") - } - } - } - - math["randomseed"] = luaFunction { seed: Number -> - random = random(seed.toLong()) - } - - // TODO: NYI, maybe polyfill? - Utf8Lib.installInto(this, globals) - - // provideRootBindings(this) - // provideUtilityBindings(this) - } - - private val scripts = ObjectArraySet() - private var initCalled = false - private val loadedScripts = ObjectArraySet() - val require = LuaRequire() - - inner class LuaRequire : AbstractFunction1() { - override fun resume(context: ExecutionContext?, suspendedState: Any?) { - throw NonsuspendableFunctionException(this::class.java) - } - - override fun invoke(context: ExecutionContext, arg1: ByteString) { - val name = arg1.decode() - - if (loadedScripts.add(name)) { - val script = Starbound.loadScript(name) - executor.call(this@LuaEnvironment, script.newInstance(Variable(globals))) - } - } - } - - init { - globals["require"] = require - } - - fun attach(script: ChunkFactory) { - if (initCalled) { - executor.call(this@LuaEnvironment, script.newInstance(Variable(globals))) - } else { - scripts.add(script) - } - } - - fun attach(scripts: Collection) { - if (initCalled) { - for (name in scripts) { - if (loadedScripts.add(name.fullPath)) { - val script = Starbound.loadScript(name.fullPath) - executor.call(this@LuaEnvironment, script.newInstance(Variable(globals))) - } - } - } else { - for (script in scripts) { - if (loadedScripts.add(script.fullPath)) { - this.scripts.add(Starbound.loadScript(script.fullPath)) - } - } - } - } - - fun run(chunk: ChunkFactory): Array { - return executor.call(this, chunk.newInstance(Variable(globals))) - } - - var errorState = false - private set - - fun markErrored() { - errorState = true - } - - fun call(fn: Any, vararg args: Any?) = executor.call(this, fn, *args) - - fun init(callInit: Boolean = true): Boolean { - check(!initCalled) { "Already called init()" } - initCalled = true - - for (script in scripts) { - try { - executor.call(this, script.newInstance(Variable(globals))) - } catch (err: Throwable) { - // errorState = true - LOGGER.error("Failed to attach script to environment", err) - scripts.clear() - return false - } - } - - scripts.clear() - - if (callInit) { - val init = globals["init"] - - if (init is LuaFunction<*, *, *, *, *>) { - try { - executor.call(this, init) - } catch (err: Throwable) { - // errorState = true - LOGGER.error("Exception on init()", err) - return false - } - } - } - - return true - } - - fun invokeGlobal(name: String, vararg arguments: Any?): Array { - if (errorState || !initCalled) - return arrayOf() - - val load = globals[name] - - if (load is LuaFunction<*, *, *, *, *>) { - return try { - executor.call(this, load, *arguments) - } catch (err: Throwable) { - // errorState = true - LOGGER.error("Exception while calling global $name", err) - arrayOf() - } - } - - return arrayOf() - } - - private val loader by lazy { CompilerChunkLoader.of(CompilerSettings.defaultNoAccountingSettings(), "sb_lua${COUNTER.getAndIncrement()}_") } - - // leaks memory until LuaEnvironment goes out of scope. Too bad! - fun eval(chunk: String, name: String = "eval"): Array { - return executor.call(this, loader.compileTextChunk(chunk, name).newInstance(Variable(globals))) - } - - companion object { - private val LOGGER = LogManager.getLogger() - private val COUNTER = AtomicLong() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaMessageHandlerComponent.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaMessageHandlerComponent.kt index 0157539d..a97540b7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaMessageHandlerComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaMessageHandlerComponent.kt @@ -1,11 +1,12 @@ package ru.dbotthepony.kstarbound.lua +import com.google.gson.JsonArray import com.google.gson.JsonElement import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.util.ActionPacer import ru.dbotthepony.kstarbound.util.sbIntern -class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String) { +class LuaMessageHandlerComponent(val lua: LuaThread, val nameProvider: () -> String) { private val handlers = HashMap() private fun setHandler(args: LuaThread.ArgStack): Int { @@ -37,7 +38,7 @@ class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String) return handlers[name] } - inline fun handle(lua: LuaThread, message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? { + inline fun handle(message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? { val handler = lookupHandler(message) ?: return null val top = lua.stackTop @@ -60,6 +61,18 @@ class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String) } } + fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement? { + return handle(message, isLocal) { + ensureExtraCapacity(arguments.size() + 4) + + for (argument in arguments) { + push(argument) + } + + arguments.size() + } + } + companion object { val LOGGER = LogManager.getLogger() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt index c5e332d1..e2993cf4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt @@ -4,11 +4,12 @@ import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder import ru.dbotthepony.kstarbound.util.random.random +import java.io.Closeable import java.util.concurrent.ConcurrentLinkedQueue import java.util.random.RandomGenerator import kotlin.properties.Delegates -class LuaSharedState(val handlesThread: LuaThread) { +class LuaSharedState(val handlesThread: LuaThread) : Closeable { private val pendingFree = ConcurrentLinkedQueue() private val freeHandles = IntAVLTreeSet() private var nextHandle = 0 @@ -21,6 +22,15 @@ class LuaSharedState(val handlesThread: LuaThread) { var commonHandles by Delegates.notNull() private set + var isValid = true + private set + + override fun close() { + if (!isValid) return + isValid = false + namedHandles.clear() + } + fun initializeHandles(mainThread: LuaThread) { val future = LuaFuture.initializeHandle(mainThread) val pathFinder = LuaPathFinder.initializeHandle(mainThread) @@ -32,6 +42,7 @@ class LuaSharedState(val handlesThread: LuaThread) { } fun freeHandle(handle: Int, key: Any?) { + if (!isValid) return pendingFree.add(handle) if (key != null) { @@ -40,6 +51,7 @@ class LuaSharedState(val handlesThread: LuaThread) { } fun cleanup() { + check(isValid) { "Shared state is no longer valid" } if (handlesInUse == 0) return var handle = pendingFree.poll() @@ -55,6 +67,7 @@ class LuaSharedState(val handlesThread: LuaThread) { } fun allocateHandle(name: Any?): LuaHandle { + check(isValid) { "Shared state is no longer valid" } require(name == null || name !in namedHandles) { "Named handle '$name' already exists" } handlesInUse++ @@ -80,10 +93,12 @@ class LuaSharedState(val handlesThread: LuaThread) { } fun getNamedHandle(key: Any): LuaHandle { + check(isValid) { "Shared state is no longer valid" } return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key") } fun findNamedHandle(key: Any): LuaHandle? { + check(isValid) { "Shared state is no longer valid" } return namedHandles[key] } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt index a7354c9c..3342dc68 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt @@ -140,7 +140,10 @@ class LuaThread private constructor( } override fun close() { - this.cleanable?.clean() + if (cleanable != null) { + cleanable!!.clean() + sharedState.close() + } } val stackTop: Int get() { @@ -232,6 +235,27 @@ class LuaThread private constructor( } } + /** + * Returns boolean indicating whenever function exists + */ + fun invokeGlobal(name: String, numArguments: Int): Boolean { + val top = stackTop + + try { + val type = loadGlobal(name) + + if (type != LuaType.FUNCTION) { + pop(numArguments) + return false + } + + call(numArguments) + return true + } finally { + setTop(top) + } + } + /** * Returns empty [KOptional] if function does not exist */ @@ -279,12 +303,13 @@ class LuaThread private constructor( } } - fun eval(chunk: String, name: String = "eval") { + fun eval(chunk: String, name: String = "eval"): JsonElement { val top = stackTop try { load(chunk, name) - call() + call(numResults = 1) + return getJson() ?: JsonNull.INSTANCE } finally { setTop(top) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/PrintFunction.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/PrintFunction.kt deleted file mode 100644 index ce39a76e..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/PrintFunction.kt +++ /dev/null @@ -1,63 +0,0 @@ -package ru.dbotthepony.kstarbound.lua - -import org.apache.logging.log4j.LogManager -import org.classdump.luna.ByteString -import org.classdump.luna.Conversions -import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.runtime.AbstractFunctionAnyArg -import org.classdump.luna.runtime.Dispatch -import org.classdump.luna.runtime.ExecutionContext - -class PrintFunction(val env: Any) : AbstractFunctionAnyArg() { - private inner class State(arguments: Array) { - private val resolved = ArrayList(arguments.size) - private var tostring: Any? = null - - private val machine = StateMachine() - - init { - machine.add { Dispatch.index(it, env, "tostring") } - machine.add { - tostring = it.returnBuffer.get0() - - if (tostring == null) { - throw LuaRuntimeException("global 'tostring' is nil (required by 'print')") - } - } - - for (argument in arguments) { - machine.add { Dispatch.call(it, tostring, argument) } - machine.add { - val canonical = Conversions.canonicalRepresentationOf(it.returnBuffer.get0()) - - if (canonical is ByteString) { - resolved.add(canonical) - } else { - throw LuaRuntimeException("Illegal value returned by 'tostring' (required by 'print')") - } - } - } - - machine.add { - LOGGER.info(resolved.joinToString("\t") { it.decode() }) - it.returnBuffer.setTo() - } - } - - fun run(context: ExecutionContext) { - machine.run(context, this@PrintFunction, this) - } - } - - override fun resume(context: ExecutionContext, suspendedState: Any) { - (suspendedState as State).run(context) - } - - override fun invoke(context: ExecutionContext, args: Array) { - State(args).run(context) - } - - companion object { - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/StateMachine.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/StateMachine.kt deleted file mode 100644 index 4d4840ea..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/StateMachine.kt +++ /dev/null @@ -1,103 +0,0 @@ -package ru.dbotthepony.kstarbound.lua - -import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.Table -import org.classdump.luna.runtime.Dispatch -import org.classdump.luna.runtime.ExecutionContext -import org.classdump.luna.runtime.Resumable -import org.classdump.luna.runtime.UnresolvedControlThrowable -import ru.dbotthepony.kommons.util.KOptional -import ru.dbotthepony.kstarbound.math.vector.Vector2i -import java.util.function.Supplier - -class StateMachine : Resumable { - private val states = ArrayDeque<(ExecutionContext) -> Unit>() - - fun add(state: (ExecutionContext) -> Unit): StateMachine { - states.add(state) - return this - } - - fun mergeInto(other: StateMachine): StateMachine { - states.addAll(other.states) - return other - } - - fun mergeFrom(other: StateMachine): StateMachine { - other.mergeInto(this) - return this - } - - override fun resume(context: ExecutionContext, suspendedState: Any) { - run(context, this, this) - } - - fun run(context: ExecutionContext): Boolean = run(context, this, this) - - fun run(context: ExecutionContext, resumable: Resumable, suspendState: Any): Boolean { - if (states.isEmpty()) - return false - - while (states.isNotEmpty()) { - val next = states.removeFirst() - - try { - next.invoke(context) - } catch (err: UnresolvedControlThrowable) { - throw err.resolve(resumable, suspendState) - } - } - - return true - } - - fun index(table: Table, vararg indexes: Any): Supplier { - var value: KOptional = KOptional.empty() - - for (index in indexes) { - add { - if (!value.isPresent) { - value = KOptional.ofNullable(Dispatch.index(it, table, index)) - } - } - } - - return Supplier { - if (!value.isPresent) { - throw LuaRuntimeException("Unable to locate value in table using following keys: ${indexes.joinToString()}") - } - } - } - - fun optionalIndex(table: Table, vararg indexes: Any): Supplier> { - var value: KOptional = KOptional.empty() - - for (index in indexes) { - add { - if (!value.isPresent) { - value = KOptional.ofNullable(Dispatch.index(it, table, index)) - } - } - } - - return Supplier { value } - } - - fun loadVector2i(table: Table): Supplier { - val x = index(table, 1, "x") - val y = index(table, 2, "y") - var value = KOptional.empty() - - add { - val ix = x.get() - val iy = y.get() - - if (ix !is Number) throw LuaRuntimeException("Invalid 'x' value for vector: ${x.get()}") - if (iy !is Number) throw LuaRuntimeException("Invalid 'y' value for vector: ${x.get()}") - - value = KOptional(Vector2i(ix.toInt(), iy.toInt())) - } - - return Supplier { value.value } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MovementControllerBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MovementControllerBindings.kt index e000d5dd..5bc4c7cf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MovementControllerBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MovementControllerBindings.kt @@ -16,7 +16,7 @@ import ru.dbotthepony.kstarbound.world.entities.AnchorNetworkState import ru.dbotthepony.kstarbound.world.physics.Poly import kotlin.math.PI -class MovementControllerBindings(val self: ActorMovementController) { +class MovementControllerBindings(val self: ActorMovementController, lua: LuaThread) { private fun mass(args: LuaThread.ArgStack): Int { args.lua.push(self.mass) return 1 @@ -303,7 +303,7 @@ class MovementControllerBindings(val self: ActorMovementController) { return 0 } - fun init(lua: LuaThread) { + init { lua.pushTable() lua.dup() lua.storeGlobal("mcontroller") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt index c3e12de6..f661c036 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt @@ -4,7 +4,6 @@ import com.google.gson.JsonNull import com.google.gson.JsonObject import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import org.apache.logging.log4j.LogManager -import org.classdump.luna.LuaRuntimeException import ru.dbotthepony.kstarbound.Globals import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registry @@ -36,13 +35,11 @@ private fun lookup(registry: Registry, args: LuaThread.ArgStack): R private fun lookupStrict(registry: Registry, args: LuaThread.ArgStack): Registry.Entry { return when (val type = args.peek()) { LuaType.NUMBER -> { - val key = args.nextInt() - registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key") + registry.getOrThrow(args.nextInt()) } LuaType.STRING -> { - val key = args.nextString() - registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key") + registry.getOrThrow(args.nextString()) } else -> throw IllegalArgumentException("Invalid registry key type: $type") @@ -51,8 +48,7 @@ private fun lookupStrict(registry: Registry, args: LuaThread.ArgSta private fun registryDef(registry: Registry<*>): LuaThread.Fn { return LuaThread.Fn { args -> - val name = args.nextString() - val value = registry[name] ?: throw LuaRuntimeException("No such ${registry.name}: $name") + val value = registry.getOrThrow(args.nextString()) args.lua.push(value.json) return@Fn 1 } @@ -252,7 +248,7 @@ private fun createItem(args: LuaThread.ArgStack): Int { val seed = args.nextOptionalLong() if (desc.name !in ItemRegistry) { - throw LuaRuntimeException("No such item ${desc.name}") + throw NoSuchElementException("No such item ${desc.name}") } else { val (_, params) = desc.buildConfig(level, seed) @@ -348,7 +344,7 @@ private fun loadVersionedJson(args: LuaThread.ArgStack): Int { private fun evalFunction(args: LuaThread.ArgStack): Int { val name = args.nextString() val value = args.nextDouble() - val fn = Registries.jsonFunctions[name] ?: throw LuaRuntimeException("No such function $name") + val fn = Registries.jsonFunctions[name] ?: throw NoSuchElementException("No such function $name") args.lua.push(fn.value.evaluate(value)) return 1 } @@ -357,7 +353,7 @@ private fun evalFunction2(args: LuaThread.ArgStack): Int { val name = args.nextString() val value = args.nextDouble() val value2 = args.nextDouble() - val fn = Registries.json2Functions[name] ?: throw LuaRuntimeException("No such function $name") + val fn = Registries.json2Functions[name] ?: throw NoSuchElementException("No such function $name") args.lua.push(fn.value.evaluate(value, value2)) return 1 } @@ -365,7 +361,7 @@ private fun evalFunction2(args: LuaThread.ArgStack): Int { private fun imageSize(args: LuaThread.ArgStack): Int { val name = args.nextString() val ref = SpriteReference.create(name) - val sprite = ref.sprite ?: throw LuaRuntimeException("No such image or sprite $ref") + val sprite = ref.sprite ?: throw NoSuchElementException("No such image or sprite $ref") args.lua.pushTable(2) args.lua.setTableValue(1, sprite.width) args.lua.setTableValue(2, sprite.height) @@ -374,7 +370,7 @@ private fun imageSize(args: LuaThread.ArgStack): Int { private fun imageSpaces(args: LuaThread.ArgStack): Int { val name = args.nextString() - val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name") + val image = Image.get(name) ?: throw NoSuchElementException("No such image $name") val pixelOffset = args.nextVector2i() val fillFactor = args.nextDouble() @@ -394,7 +390,7 @@ private fun imageSpaces(args: LuaThread.ArgStack): Int { private fun nonEmptyRegion(args: LuaThread.ArgStack): Int { val name = args.nextString() - val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name") + val image = Image.get(name) ?: throw NoSuchElementException("No such image $name") args.lua.push(image.nonEmptyRegion) return 1 } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/ServerWorldBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/ServerWorldBindings.kt index 4b1680a9..40a1d652 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/ServerWorldBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/ServerWorldBindings.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.kstarbound.lua.bindings import org.apache.logging.log4j.LogManager -import org.classdump.luna.LuaRuntimeException import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Starbound @@ -18,7 +17,6 @@ import ru.dbotthepony.kstarbound.lua.nextAABBi import ru.dbotthepony.kstarbound.lua.nextVector2d import ru.dbotthepony.kstarbound.lua.nextVector2i import ru.dbotthepony.kstarbound.lua.push -import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture import ru.dbotthepony.kstarbound.lua.userdata.push import ru.dbotthepony.kstarbound.server.world.ServerWorld @@ -239,10 +237,7 @@ private fun players(self: ServerWorld, args: LuaThread.ArgStack): Int { private fun setSkyTime(self: ServerWorld, args: LuaThread.ArgStack): Int { val time = args.nextDouble() - - if (time < 0.0) - throw LuaRuntimeException("Negative time? $time") - + require(time >= 0.0) { "Negative time? $time" } self.sky.time = time return 0 } @@ -301,7 +296,7 @@ private fun setDungeonGravity(self: ServerWorld, args: LuaThread.ArgStack): Int } else if (peek == LuaType.TABLE) { self.setDungeonGravity(id, args.nextVector2d()) } else { - throw LuaRuntimeException("Illegal gravity argument to setDungeonGravity: $peek") + throw IllegalArgumentException("Illegal gravity argument to setDungeonGravity: $peek") } return 0 @@ -336,7 +331,7 @@ private fun enqueuePlacement(self: ServerWorld, args: LuaThread.ArgStack): Int { for (v in distributions) { // original engine treats distributions table as if it was originating from biome json files - val unprepared = Starbound.gson.fromJson(toJsonFromLua(v), BiomePlaceablesDefinition.DistributionItem::class.java) + val unprepared = Starbound.gson.fromJson(v, BiomePlaceablesDefinition.DistributionItem::class.java) val prepared = unprepared.create(BiomeDefinition.CreationParams(hueShift = 0.0, random = args.lua.random)) items.add(prepared) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt index 1b04311f..07f8258a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt @@ -21,7 +21,6 @@ import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.lua.LuaHandle import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaType -import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.util.valueOf private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Map): NodeParameterValue { @@ -246,7 +245,7 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int { } else { mergedParams = Starbound.gson.fromJsonFast(base.json.deepCopy().also { it as JsonObject - it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters)) + it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, parameters) }, BehaviorDefinition::class.java) } } else { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWireProcessor.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWireProcessor.kt index 0471f2b8..bf11fa55 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWireProcessor.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWireProcessor.kt @@ -5,8 +5,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import org.apache.logging.log4j.LogManager -import org.classdump.luna.ByteString -import ru.dbotthepony.kstarbound.lua.tableMapOf import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject @@ -99,7 +97,10 @@ class LegacyWireProcessor(val world: ServerWorld) { if (newState != node.state) { try { node.state = newState - entity.lua.invokeGlobal("onInputNodeChange", entity.lua.tableMapOf(NODE_KEY to i.toLong(), LEVEL_KEY to newState)) + entity.lua.pushTable(hashSize = 2) + entity.lua.setTableValue("node", i) + entity.lua.setTableValue("level", newState) + entity.lua.invokeGlobal("onInputNodeChange", 1) } catch (err: Throwable) { LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err) } @@ -159,8 +160,6 @@ class LegacyWireProcessor(val world: ServerWorld) { } companion object { - val NODE_KEY: ByteString = ByteString.of("node") - val LEVEL_KEY: ByteString = ByteString.of("level") private val LOGGER = LogManager.getLogger() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt index a52e4979..ed54423b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt @@ -215,6 +215,8 @@ class ServerWorld private constructor( it.client.enqueueWarp(WarpAlias.Return) } + callUninitOnEntities() + if (!uncleanShutdown) { saveMetadata() storage.commit() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RandomUtils.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RandomUtils.kt index f6ca3d4e..f1b2da7c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RandomUtils.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RandomUtils.kt @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableCollection import com.google.gson.JsonArray import com.google.gson.JsonElement import it.unimi.dsi.fastutil.bytes.ByteConsumer -import org.classdump.luna.ByteString import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2f import ru.dbotthepony.kommons.util.IStruct2i @@ -76,7 +75,6 @@ fun staticRandom32FromList(values: Iterable): Int { for (value in values) { when (value) { is String -> digest.update(value.toByteArray()) - is ByteString -> digest.update(value.bytes) is Byte -> digest.update(value) is Boolean -> digest.update(if (value) 1 else 0) is Short -> toBytes(digest::update, value) @@ -134,7 +132,6 @@ fun staticRandom64FromList(values: Iterable): Long { for (value in values) { when (value) { is String -> digest.update(value.toByteArray()) - is ByteString -> digest.update(value.bytes) is Byte -> digest.update(value) is Boolean -> digest.update(if (value) 1 else 0) is Short -> toBytes(digest::update, value) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index 2c24385a..b79fecda 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -566,6 +566,16 @@ abstract class World, ChunkType : Chunk { protected open fun onJoinWorld(world: World<*, *>) { } /** - * Called upon being removed from [world], regardless if entity is local or not + * Called upon being removed from [world], regardless if entity is local or not, + * but **not** when world is being unloaded from memory ([uninit] should be used in latter case) */ protected open fun onRemove(world: World<*, *>, reason: RemovalReason) { } + /** + * Called upon being removed from [world], regardless if entity is local or not, + * including when world is being unloaded from memory + * + * When being removed from world, called right after [onRemove] + * + * Should be used to free native data structures, or remove data from global structures + */ + open fun uninit(world: World<*, *>) { } + val networkGroup = MasterElement(NetworkedGroup()) abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) @@ -286,6 +297,12 @@ abstract class AbstractEntity : Comparable { LOGGER.error("Exception while removing $this from $world", err) } + try { + uninit(world) + } catch (err: Throwable) { + LOGGER.error("Exception while calling uninit on $this from $world", err) + } + spatialEntry?.remove() spatialEntry = null innerWorld = null diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt index 4507a369..3b8449e1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt @@ -2,23 +2,15 @@ package ru.dbotthepony.kstarbound.world.entities import com.google.gson.JsonArray import com.google.gson.JsonElement -import org.classdump.luna.ByteString -import org.classdump.luna.Table -import ru.dbotthepony.kommons.util.IStruct2d -import ru.dbotthepony.kstarbound.defs.DamageNotification -import ru.dbotthepony.kstarbound.defs.DamageSource +import com.google.gson.JsonNull +import com.google.gson.JsonPrimitive import ru.dbotthepony.kstarbound.defs.Drawable import ru.dbotthepony.kstarbound.defs.InteractAction import ru.dbotthepony.kstarbound.defs.InteractRequest import ru.dbotthepony.kstarbound.json.builder.IStringSerializable -import ru.dbotthepony.kstarbound.lua.LuaEnvironment -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.get -import ru.dbotthepony.kstarbound.lua.tableMapOf -import ru.dbotthepony.kstarbound.lua.toJsonFromLua +import ru.dbotthepony.kstarbound.lua.setTableValue import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket -import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.World @@ -35,8 +27,6 @@ abstract class ActorEntity : DynamicEntity(), InteractiveEntity, ScriptedEntity // ticked manually in final classes, to ensure proper order of operations val effects = EffectEmitter(this) - abstract val lua: LuaEnvironment - override fun move(delta: Double) { statusController.applyMovementControls() super.move(delta) @@ -66,6 +56,11 @@ abstract class ActorEntity : DynamicEntity(), InteractiveEntity, ScriptedEntity statusController.init() } + override fun uninit(world: World<*, *>) { + super.uninit(world) + statusController.uninit() + } + override fun tick(delta: Double) { super.tick(delta) @@ -114,31 +109,34 @@ abstract class ActorEntity : DynamicEntity(), InteractiveEntity, ScriptedEntity } override fun callScript(fnName: String, arguments: JsonArray): JsonElement { - //require(isLocal) { "Calling script on remote entity" } - //return lua.invokeGlobal(fnName, *arguments) - TODO() + require(isLocal) { "Calling script on remote entity" } + return super.callScript(fnName, arguments) } - override fun evalScript(code: String): Array { + override fun evalScript(code: String): JsonElement { require(isLocal) { "Calling script on remote entity" } - return lua.eval(code) + return super.evalScript(code) } override fun interact(request: InteractRequest): InteractAction { - val result = lua.invokeGlobal("interact", lua.tableMapOf( - "sourceId" to request.source, - "sourcePosition" to lua.from(request.sourcePos) - )) + val result = lua.invokeGlobal("interact", 1, { + pushTable(hashSize = 2) - if (result.isEmpty() || result[0] == null) + setTableValue("sourcePosition", request.sourcePos) + setTableValue("sourceId", request.source) + + 1 + }, { getJson() ?: JsonNull.INSTANCE }) + + if (result.isEmpty || result.value.isJsonNull) return InteractAction.NONE - val value = result[0] + val value = result.value - if (value is ByteString) - return InteractAction(value.decode(), entityID) + if (value is JsonPrimitive) + return InteractAction(value.asString, entityID) - value as Table - return InteractAction((value[1L] as ByteString).decode(), entityID, toJsonFromLua(value[2L])) + value as JsonArray + return InteractAction(value[1].asString, entityID, if (value.size() >= 2) value[2] else JsonNull.INSTANCE) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt index 7537e07c..d84d5e60 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt @@ -21,14 +21,14 @@ abstract class DynamicEntity() : AbstractEntity() { abstract val movement: MovementController /** - * Called in multiple threads + * Called in multiple threads, when [isLocal] is true */ protected open fun move(delta: Double) { movement.move(delta) } /** - * Called in multiple threads + * Called in multiple threads, when [isLocal] is false */ protected open fun moveRemote(delta: Double) { movement.tickRemote(delta) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt index d69d8cd9..88f6293e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt @@ -8,11 +8,8 @@ import com.google.gson.JsonElement import com.google.gson.JsonNull import com.google.gson.JsonObject import it.unimi.dsi.fastutil.objects.ObjectArraySet -import org.classdump.luna.ByteString -import org.classdump.luna.Table import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.set -import ru.dbotthepony.kstarbound.io.map import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue @@ -26,8 +23,6 @@ import ru.dbotthepony.kstarbound.defs.DamageSource import ru.dbotthepony.kstarbound.defs.EntityDamageTeam import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.HitType -import ru.dbotthepony.kstarbound.defs.InteractAction -import ru.dbotthepony.kstarbound.defs.InteractRequest import ru.dbotthepony.kstarbound.defs.JumpProfile import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion import ru.dbotthepony.kstarbound.defs.actor.StatModifier @@ -38,21 +33,17 @@ import ru.dbotthepony.kstarbound.defs.monster.MonsterVariant import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.io.DoubleValueCodec import ru.dbotthepony.kstarbound.io.FloatValueCodec +import ru.dbotthepony.kstarbound.io.map import ru.dbotthepony.kstarbound.io.nullable import ru.dbotthepony.kstarbound.json.builder.JsonFactory -import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent +import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings -import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBindings +import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBinding import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.get -import ru.dbotthepony.kstarbound.lua.set -import ru.dbotthepony.kstarbound.lua.tableMapOf -import ru.dbotthepony.kstarbound.lua.toJsonFromLua -import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState +import ru.dbotthepony.kstarbound.lua.userdata.provideBehaviorBindings import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket @@ -70,9 +61,9 @@ import ru.dbotthepony.kstarbound.util.random.MWCRandom import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.World -import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity import ru.dbotthepony.kstarbound.world.physics.Poly import java.io.DataOutputStream +import kotlin.properties.Delegates class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorEntity() { override val type: EntityType @@ -82,16 +73,17 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE team.accept(EntityDamageTeam(variant.commonParameters.damageTeamType, variant.commonParameters.damageTeam)) } - override val lua = LuaEnvironment() - val luaUpdate = LuaUpdateComponent(lua) - val luaMovement = MovementControllerBindings(movement) - val luaMessages = LuaMessageHandlerComponent(lua) { toString() } + private var scriptStorage = JsonObject() + override var lua by Delegates.notNull() + private set + var luaUpdate by Delegates.notNull() + private set + var luaMovement by Delegates.notNull() + private set + var luaMessages by Delegates.notNull() + private set val animator = Animator(variant.animationConfig) - init { - lua.globals["storage"] = lua.newTable() - } - override fun move(delta: Double) { luaMovement.apply() super.move(delta) @@ -169,7 +161,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE this.uniqueID.value = deserialized.uniqueId this.team.value = deserialized.team - this.lua.globals["storage"] = lua.from(deserialized.scriptStorage) + scriptStorage = deserialized.scriptStorage as? JsonObject ?: JsonObject() } override fun serialize(data: JsonObject) { @@ -188,7 +180,10 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE uniqueID.value, team.value, effectEmitter = effects.serialize(), - scriptStorage = toJsonFromLua(lua.globals["storage"]), + scriptStorage = run { + lua.loadGlobal("storage") + lua.popJson() ?: JsonObject() + }, ) for ((k, v) in Starbound.gson.toJsonTree(moreData).asJsonObject.entrySet()) { @@ -263,24 +258,41 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE monsterLevel = monsterLevel ?: world.template.threatLevel - if (!isRemote) { + if (isLocal) { val healthMultiplier = (variant.commonParameters.healthLevelFunction.value?.evaluate(monsterLevel!!) ?: 1.0) * variant.commonParameters.healthMultiplier statusController.setPersistentEffects("innate", listOf(Either.left(StatModifier("maxHealth", healthMultiplier, StatModifierType.BASE_MULTIPLICATION)))) + lua = LuaThread() + luaUpdate = LuaUpdateComponent(lua, this) + luaMovement = MovementControllerBindings(movement, lua) + luaMessages = LuaMessageHandlerComponent(lua) { toString() } + + lua.push(scriptStorage) + lua.storeGlobal("storage") + // free up memory + scriptStorage = JsonObject() + provideEntityBindings(this, lua) provideAnimatorBindings(animator, lua) - provideConfigBindings(lua) { key, default -> - key.find(variant.parameters) ?: default + provideConfigBinding(lua) { key -> + key.find(variant.parameters) } lua.attach(variant.commonParameters.scripts) luaUpdate.stepCount = variant.commonParameters.initialScriptDelta - BehaviorState.provideBindings(lua) + provideBehaviorBindings(lua) - luaMovement.init(lua) - lua.init() + lua.initScripts() + } + } + + override fun uninit(world: World<*, *>) { + super.uninit(world) + + if (isLocal) { + lua.close() } } @@ -320,12 +332,13 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE // TODO: hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit maybe? if (totalDamage > 0.0) { - lua.invokeGlobal("damage", lua.tableMapOf( - "sourceId" to damage.request.sourceEntityId, - "damage" to totalDamage, - "sourceDamage" to damage.request.damage, - "sourceKind" to damage.request.damageSourceKind - )) + lua.pushTable(hashSize = 4) + lua.setTableValue("sourceId", damage.request.sourceEntityId) + lua.setTableValue("damage", totalDamage) + lua.setTableValue("sourceDamage", damage.request.damage) + lua.setTableValue("sourceKind", damage.request.damageSourceKind) + + lua.invokeGlobal("damage", 1) } if (health <= 0.0) { @@ -336,8 +349,8 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE } private val shouldDie: Boolean get() { - val result = lua.invokeGlobal("shouldDie") - return result.isNotEmpty() && result[0] is Boolean && result[0] as Boolean || health <= 0.0 //|| lua.errorState + val result = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false) + return result || health <= 0.0 //|| lua.errorState } override fun tickParallel(delta: Double) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt index b69efc98..029c8cb5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/NPCEntity.kt @@ -6,8 +6,6 @@ import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive -import org.classdump.luna.ByteString -import org.classdump.luna.Table import ru.dbotthepony.kommons.collect.filterNotNull import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.util.getValue @@ -22,8 +20,6 @@ import ru.dbotthepony.kstarbound.defs.DamageSource import ru.dbotthepony.kstarbound.defs.EntityDamageTeam import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.HitType -import ru.dbotthepony.kstarbound.defs.InteractAction -import ru.dbotthepony.kstarbound.defs.InteractRequest import ru.dbotthepony.kstarbound.defs.actor.HumanoidConfig import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote import ru.dbotthepony.kstarbound.defs.actor.HumanoidIdentity @@ -35,19 +31,13 @@ import ru.dbotthepony.kstarbound.io.BinaryStringCodec import ru.dbotthepony.kstarbound.io.NullableBinaryStringCodec import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.mergeJson -import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent +import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings -import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBindings +import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBinding import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.get -import ru.dbotthepony.kstarbound.lua.set -import ru.dbotthepony.kstarbound.lua.tableMapOf -import ru.dbotthepony.kstarbound.lua.tableOf -import ru.dbotthepony.kstarbound.lua.toJsonFromLua -import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState +import ru.dbotthepony.kstarbound.lua.userdata.provideBehaviorBindings import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.math.vector.Vector2d @@ -61,12 +51,10 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedJsonElement import ru.dbotthepony.kstarbound.network.syncher.networkedString import ru.dbotthepony.kstarbound.util.GameTimer import ru.dbotthepony.kstarbound.util.random.staticRandomInt -import ru.dbotthepony.kstarbound.util.valueOf import ru.dbotthepony.kstarbound.world.World -import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity -import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity import java.io.DataOutputStream import kotlin.math.absoluteValue +import kotlin.properties.Delegates class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { override val type: EntityType @@ -155,14 +143,15 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { override var isPersistent: Boolean = variant.persistent override var keepAlive: Boolean = variant.keepAlive - override val lua = LuaEnvironment() - val luaUpdate = LuaUpdateComponent(lua) - val luaMovement = MovementControllerBindings(movement) - val luaMessages = LuaMessageHandlerComponent(lua) { toString() } - - init { - lua.globals["storage"] = lua.tableOf() - } + private var scriptStorage = JsonObject() + override var lua by Delegates.notNull() + private set + var luaUpdate by Delegates.notNull() + private set + var luaMovement by Delegates.notNull() + private set + var luaMessages by Delegates.notNull() + private set override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? { return luaMessages.handle(message, connection == connectionID, arguments) ?: statusController.handleMessage(message, connection == connectionID, arguments) @@ -201,6 +190,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { val deathParticleBurst: String? = null, val dropPools: ImmutableList> = ImmutableList.of(), val aggressive: Boolean = false, + val scriptStorage: JsonObject = JsonObject(), ) override fun deserialize(data: JsonObject) { @@ -231,6 +221,8 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { dropPools.addAll(serialized.dropPools.filter { it.isPresent }) isAggressive = serialized.aggressive blinkTimer.reset(0.0) + + scriptStorage = serialized.scriptStorage } override fun serialize(data: JsonObject) { @@ -257,6 +249,10 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { deathParticleBurst = deathParticleBurst, dropPools = dropPools.stream().filter { it.isPresent }.collect(ImmutableList.toImmutableList()), aggressive = isAggressive, + scriptStorage = run { + lua.loadGlobal("storage") + lua.popJson() as? JsonObject ?: JsonObject() + } ) mergeJson(data, Starbound.gson.toJsonTree(serialized)) @@ -265,24 +261,41 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { override fun onJoinWorld(world: World<*, *>) { super.onJoinWorld(world) - if (!isRemote) { + if (isLocal) { for ((slot, item) in variant.items) { setItem(slot, item) } + lua = LuaThread() + luaUpdate = LuaUpdateComponent(lua, this) + luaMovement = MovementControllerBindings(movement, lua) + luaMessages = LuaMessageHandlerComponent(lua) { toString() } + + lua.push(scriptStorage) + lua.storeGlobal("storage") + // free up memory + scriptStorage = JsonObject() + provideEntityBindings(this, lua) - provideConfigBindings(lua) { key, default -> - key.find(variant.scriptConfig) ?: default + provideConfigBinding(lua) { key -> + key.find(variant.scriptConfig) } - BehaviorState.provideBindings(lua) + provideBehaviorBindings(lua) luaUpdate.stepCount = variant.initialScriptDelta lua.attach(variant.scripts) - luaMovement.init(lua) - lua.init() + lua.initScripts() + } + } + + override fun uninit(world: World<*, *>) { + super.uninit(world) + + if (isLocal) { + lua.close() } } @@ -319,12 +332,13 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { val totalDamage = notifications.sumOf { it.healthLost } if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) { - lua.invokeGlobal("damage", lua.tableMapOf( - "sourceId" to damage.request.sourceEntityId, - "damage" to totalDamage, - "sourceDamage" to damage.request.damage, - "sourceKind" to damage.request.damageSourceKind - )) + lua.pushTable(hashSize = 4) + lua.setTableValue("sourceId", damage.request.sourceEntityId) + lua.setTableValue("damage", totalDamage) + lua.setTableValue("sourceDamage", damage.request.damage) + lua.setTableValue("sourceKind", damage.request.damageSourceKind) + + lua.invokeGlobal("damage", 1) } return notifications @@ -388,14 +402,14 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { override fun tick(delta: Double) { super.tick(delta) - if (!isRemote) { - if (isDead || lua.errorState) { + if (isLocal) { + if (isDead) { remove(RemovalReason.DYING) return } else { - val shouldDie = lua.invokeGlobal("shouldDie") + val shouldDie = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false) - if (shouldDie.isNotEmpty() && shouldDie[0] is Boolean && shouldDie[0] as Boolean) { + if (shouldDie) { remove(RemovalReason.DYING) return } @@ -410,7 +424,8 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { override fun onRemove(world: World<*, *>, reason: RemovalReason) { super.onRemove(world, reason) - lua.invokeGlobal("die") + if (isLocal) + lua.invokeGlobal("die", 0) val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathFinder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathFinder.kt index 7c00f892..0fe5d5b7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathFinder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathFinder.kt @@ -1,36 +1,25 @@ package ru.dbotthepony.kstarbound.world.entities -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import org.apache.logging.log4j.LogManager -import org.classdump.luna.Table -import org.classdump.luna.TableFactory import ru.dbotthepony.kommons.collect.map import ru.dbotthepony.kommons.collect.reduce import ru.dbotthepony.kommons.util.KOptional -import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.lua.LuaThread -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.setTableValue -import ru.dbotthepony.kstarbound.lua.tableOf -import ru.dbotthepony.kstarbound.lua.toByteString import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.math.AABBi import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2i -import ru.dbotthepony.kstarbound.util.CarriedExecutor -import ru.dbotthepony.kstarbound.util.supplyAsync import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.physics.CollisionType -import java.util.PriorityQueue +import java.util.* import java.util.function.Supplier import kotlin.math.PI import kotlin.math.absoluteValue import kotlin.math.min -import kotlin.math.roundToInt import kotlin.math.sign import kotlin.math.sqrt @@ -48,8 +37,6 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d SWIM(false, "Swim"), FLY(false, "Fly"), LAND(false, "Land"); - - val luaName = jsonName.toByteString()!! } data class Edge( @@ -154,13 +141,6 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d var totalCost: Double = 0.0 var parent: Edge? = null - fun toTable(tables: TableFactory): Table { - val table = tables.newTable(2, 0) - table[positionIndex] = tables.from(position) - table[velocityIndex] = tables.from(velocity) - return table - } - fun store(lua: LuaThread) { lua.pushTable(hashSize = 2) lua.setTableValue("position", position) @@ -721,8 +701,5 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d private val LOGGER = LogManager.getLogger() const val ARC_SIMULATION_FIDELTITY = 0.5 const val NODE_GRANULARITY = 1.0 - - private val positionIndex = "position".toByteString()!! - private val velocityIndex = "velocity".toByteString()!! } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StagehandEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StagehandEntity.kt index da2a997a..b95e639e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StagehandEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StagehandEntity.kt @@ -19,13 +19,8 @@ import ru.dbotthepony.kstarbound.json.jsonArrayOf import ru.dbotthepony.kstarbound.json.putAll import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement -import ru.dbotthepony.kstarbound.lua.LuaEnvironment +import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.get -import ru.dbotthepony.kstarbound.lua.set -import ru.dbotthepony.kstarbound.lua.tableOf -import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.network.syncher.networkedFloat @@ -33,6 +28,7 @@ import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity import java.io.DataInputStream import java.io.DataOutputStream +import kotlin.properties.Delegates class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEntity { constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readJsonElement().asJsonObject, true) @@ -69,12 +65,11 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt var isScripted = false private set - val lua = LuaEnvironment() - val luaUpdate = LuaUpdateComponent(lua) + override var lua by Delegates.notNull() + private set - init { - lua.globals["storage"] = lua.tableOf() - } + var luaUpdate by Delegates.notNull() + private set override fun deserialize(data: JsonObject) { super.deserialize(data) @@ -98,22 +93,32 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt } boundingBox = broadcastArea ?: AABB.withSide(Vector2d.ZERO, 5.0) - - if (isScripted && isLocal) { - lua.attach(config["scripts"].asJsonArray.map { AssetPath(it.asString) }) - luaUpdate.stepCount = config.get("scriptDelta", 5.0) - - if ("scriptStorage" in config) { - lua.globals["storage"] = lua.from(config["scriptStorage"]) - } - } } override fun onJoinWorld(world: World<*, *>) { super.onJoinWorld(world) if (isLocal && isScripted) { - lua.init() + lua = LuaThread() + luaUpdate = LuaUpdateComponent(lua, this) + lua.attach(config["scripts"].asJsonArray.map { AssetPath(it.asString) }) + luaUpdate.stepCount = config.get("scriptDelta", 5.0) + + if ("scriptStorage" in config) { + lua.push(config["scriptStorage"]) + lua.storeGlobal("storage") + } else { + lua.pushTable() + lua.storeGlobal("storage") + } + + lua.initScripts() + } + } + + override fun uninit(world: World<*, *>) { + if (isLocal && isScripted) { + lua.close() } } @@ -128,8 +133,10 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt else data.remove("uniqueId") - if (isScripted) - data["scriptStorage"] = toJsonFromLua(lua.globals["storage"]) + if (isScripted) { + lua.loadGlobal("storage") + data["scriptStorage"] = lua.popJson() + } } override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) { @@ -146,10 +153,12 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt get() = "Technical entity responsible for doing cool stuff" override fun callScript(fnName: String, arguments: JsonArray): JsonElement { - TODO() + require(isLocal) { "Calling script on remote entity" } + return super.callScript(fnName, arguments) } - override fun evalScript(code: String): Array { - return lua.eval(code) + override fun evalScript(code: String): JsonElement { + require(isLocal) { "Calling script on remote entity" } + return super.evalScript(code) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StatusController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StatusController.kt index 7f2b21ae..ceb9b845 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StatusController.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StatusController.kt @@ -9,12 +9,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet import org.apache.logging.log4j.LogManager -import org.classdump.luna.ByteString -import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.Table import ru.dbotthepony.kommons.collect.ListenableMap -import ru.dbotthepony.kommons.collect.collect -import ru.dbotthepony.kommons.collect.map import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.io.readKOptional @@ -42,19 +37,14 @@ import ru.dbotthepony.kstarbound.io.StreamCodec import ru.dbotthepony.kstarbound.io.nullable import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.json.builder.JsonFactory -import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent +import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings import ru.dbotthepony.kstarbound.lua.bindings.createConfigBinding import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings -import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBindings +import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBinding import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.iterator -import ru.dbotthepony.kstarbound.lua.luaFunction -import ru.dbotthepony.kstarbound.lua.set -import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket import ru.dbotthepony.kstarbound.network.syncher.NetworkedDynamicGroup @@ -74,7 +64,7 @@ import ru.dbotthepony.kstarbound.world.entities.api.StatusEffectEntity import java.io.DataInputStream import java.io.DataOutputStream import java.util.* -import java.util.stream.Collectors +import kotlin.properties.Delegates // this is unnatural to have this class separated, but since it contains // lots of internal state, it would be nice to have it encapsulated; @@ -131,26 +121,39 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf networkGroup.add(statNetworkGroup) } - val lua = LuaEnvironment() - val luaUpdate = LuaUpdateComponent(lua) - val luaMovement = MovementControllerBindings(entity.movement) - val luaMessages = LuaMessageHandlerComponent(lua) { toString() } + var lua by Delegates.notNull() + private set + var luaUpdate by Delegates.notNull() + private set + var luaMovement by Delegates.notNull() + private set + var luaMessages by Delegates.notNull() + private set private val animator: EffectAnimator? private val animatorID: Int? + fun uninit() { + if (entity.isLocal) { + lua.close() + } + } + fun init() { - if (!entity.isRemote) { + if (entity.isLocal) { + lua = LuaThread() + luaUpdate = LuaUpdateComponent(lua, this) + luaMovement = MovementControllerBindings(entity.movement, lua) + luaMessages = LuaMessageHandlerComponent(lua) { toString() } luaUpdate.stepCount = config.primaryScriptDelta lua.attach(config.primaryScriptSources) // provideStatusControllerBindings(this, lua) // provided through provideEntityBindings provideEntityBindings(entity, lua) - luaMovement.init(lua) // TODO: Once we have brand new object-oriented Lua API, expose proper entity bindings here // TODO: Expose world bindings - lua.init() + lua.initScripts() } } @@ -180,14 +183,17 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf fun recentDamageDealt(since: Long = 0L) = recentDamageDealt.query(since) fun experienceDamage(damage: DamageData): List { - val results = lua.invokeGlobal("applyDamageRequest", lua.from(Starbound.gson.toJsonTree(damage))) + val results = lua.invokeGlobal("applyDamageRequest", 1, { + push(Starbound.gson.toJsonTree(damage)) + 1 + }, { getJson() as? JsonArray }).flatMap { KOptional.ofNullable(it) } - if (results.isNotEmpty() && results[0] is Table) { + if (results.isPresent) { val parsed = ArrayList() - for ((_, v) in (results[0] as Table)) { + for (v in results.value) { try { - parsed.add(Starbound.gson.fromJsonFast(toJsonFromLua(v), DamageNotification::class.java)) + parsed.add(Starbound.gson.fromJsonFast(v, DamageNotification::class.java)) } catch (err: Throwable) { LOGGER.error("Exception while parsing results returned by applyDamageRequest (primary scripts: ${config.primaryScriptSources})", err) } @@ -457,10 +463,14 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf var parentDirectives: String = "" val modifierGroups = IntArrayList() - val lua = LuaEnvironment() - val luaMessages = LuaMessageHandlerComponent(lua) { "Unique effect" } - val luaMovement = MovementControllerBindings(entity.movement) - val luaUpdate = LuaUpdateComponent(lua) + var lua by Delegates.notNull() + private set + var luaUpdate by Delegates.notNull() + private set + var luaMovement by Delegates.notNull() + private set + var luaMessages by Delegates.notNull() + private set val metadata = UniqueEffectNetworkedValues() val metadataNetworkID = uniqueEffectMetadata.add(metadata) @@ -485,14 +495,18 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf animatorNetworkID = null } - if (!entity.isRemote && effect.value.scripts.isNotEmpty()) { + if (entity.isLocal && effect.value.scripts.isNotEmpty()) { + lua = LuaThread() + luaMessages = LuaMessageHandlerComponent(lua) { "Unique effect" } + luaMovement = MovementControllerBindings(entity.movement, lua) + luaUpdate = LuaUpdateComponent(lua, this) + // unique effects shouldn't be initialized on remotes in first place // but whatever lua.attach(effect.value.scripts) // provideStatusControllerBindings(this@StatusController, lua) // provided through provideEntityBindings provideEntityBindings(entity, lua) - luaMovement.init(lua) provideEffectBindings() if (animator != null) { @@ -502,84 +516,103 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf provideAnimatorBindings(animator.animator, lua) } - provideConfigBindings(lua) { path, default -> - path.find(effect.json) ?: default + provideConfigBinding(lua) { path -> + path.find(effect.json) } - lua.init() + lua.initScripts() } } + private fun duration(args: LuaThread.ArgStack): Int { + args.lua.push(metadata.duration) + return 1 + } + + private fun modifyDuration(args: LuaThread.ArgStack): Int { + val duration = args.nextDouble() + require(duration.isFinite()) { "Infinite duration provided" } + require(!duration.isNaN()) { "NaN duration provided" } + + if (!isPersistent) + metadata.duration += duration + + return 0 + } + + private fun expire(args: LuaThread.ArgStack): Int { + if (!isPersistent) + metadata.duration = 0.0 + + return 0 + } + + private fun sourceEntity(args: LuaThread.ArgStack): Int { + args.lua.push(metadata.sourceEntity?.toLong() ?: entity.entityID.toLong()) + return 1 + } + + private fun setParentDirectives(args: LuaThread.ArgStack): Int { + parentDirectives = args.nextString().sbIntern() + return 0 + } + + private fun addStatModifierGroup(args: LuaThread.ArgStack): Int { + val modifiers = args.readTableValues { + Starbound.gson.fromJsonFast(getJson(it)!!, StatModifier::class.java) + } + + val id = statModifiers.add(ArrayList(modifiers)) + modifierGroups.add(id) + args.lua.push(id.toLong()) + return 1 + } + + private fun setStatModifierGroup(args: LuaThread.ArgStack): Int { + val id = args.nextInt() + + if (id !in modifierGroups) + throw IllegalStateException("$id stat modifier group does not belong to this effect") + + val modifiers = args.readTableValues { + Starbound.gson.fromJsonFast(getJson(it)!!, StatModifier::class.java) + } + + statModifiers[id] = ArrayList(modifiers) + return 0 + } + + private fun removeStatModifierGroup(args: LuaThread.ArgStack): Int { + val id = args.nextInt() + + if (modifierGroups.rem(id)) { + statModifiers.remove(id) + } else { + throw IllegalStateException("$id stat modifier group does not belong to this effect") + } + + return 0 + } + private fun provideEffectBindings() { - val callbacks = lua.newTable() - lua.globals["effect"] = callbacks + lua.pushTable() + lua.dup() + lua.storeGlobal("effect") - callbacks["duration"] = luaFunction { - returnBuffer.setTo(metadata.duration) - } + lua.setTableValue("duration", ::duration) + lua.setTableValue("modifyDuration", ::modifyDuration) + lua.setTableValue("expire", ::expire) + lua.setTableValue("sourceEntity", ::sourceEntity) + lua.setTableValue("setParentDirectives", ::setParentDirectives) + lua.setTableValue("addStatModifierGroup", ::addStatModifierGroup) + lua.setTableValue("setStatModifierGroup", ::setStatModifierGroup) + lua.setTableValue("removeStatModifierGroup", ::removeStatModifierGroup) - callbacks["modifyDuration"] = luaFunction { duration: Number -> - val value = duration.toDouble() - check(value.isFinite()) { "Infinite duration provided" } - check(!value.isNaN()) { "NaN duration provided" } + lua.push("getParameter") + createConfigBinding(lua) { path -> path.find(effect.json) } + lua.setTableValue() - if (!isPersistent) { - metadata.duration += value - } - } - - callbacks["expire"] = luaFunction { - if (!isPersistent) { - metadata.duration = 0.0 - } - } - - callbacks["sourceEntity"] = luaFunction { - returnBuffer.setTo(metadata.sourceEntity ?: entity.entityID) - } - - callbacks["setParentDirectives"] = luaFunction { directives: ByteString -> - parentDirectives = directives.decode().sbIntern() - } - - callbacks["getParameter"] = createConfigBinding { path, default -> - path.find(effect.json) ?: default - } - - callbacks["addStatModifierGroup"] = luaFunction { modifiers: Table -> - val actual = modifiers - .iterator() - .map { (_, v) -> Starbound.gson.fromJsonFast(toJsonFromLua(v), StatModifier::class.java) } - .collect(Collectors.toCollection(::ArrayList)) - - val id = statModifiers.add(actual) - modifierGroups.add(id) - returnBuffer.setTo(id) - } - - callbacks["setStatModifierGroup"] = luaFunction { groupID: Number, modifiers: Table -> - val id = groupID.toInt() - - if (id !in modifierGroups) - throw LuaRuntimeException("$groupID stat modifier group does not belong to this effect") - - val actual = modifiers - .iterator() - .map { (_, v) -> Starbound.gson.fromJsonFast(toJsonFromLua(v), StatModifier::class.java) } - .collect(Collectors.toCollection(::ArrayList)) - - statModifiers[id] = actual - } - - callbacks["removeStatModifierGroup"] = luaFunction { groupID: Number -> - val id = groupID.toInt() - - if (modifierGroups.rem(id)) { - statModifiers.remove(id) - } else { - throw LuaRuntimeException("$groupID stat modifier group does not belong to this effect") - } - } + lua.pop() } fun promoteToPersistent() { @@ -591,7 +624,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf // remove an ephemeral effect. // Original engine only updates the duration (setting it to null), - // but for proper promotion we also need to reset cause entity, otherwise + // but for proper promotion we also need to reset "cause" entity, otherwise // persistent effect will be sourced to whoever put ephemeral effect metadata.duration = 0.0 metadata.sourceEntity = null @@ -599,7 +632,9 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf } fun remove() { - lua.invokeGlobal("onExpire") + if (entity.isLocal) + lua.invokeGlobal("onExpire", 0) + uniqueEffectMetadata.remove(metadataNetworkID) for (group in modifierGroups) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/api/ScriptedEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/api/ScriptedEntity.kt index f237c4b1..39f08a6c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/api/ScriptedEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/api/ScriptedEntity.kt @@ -2,13 +2,29 @@ package ru.dbotthepony.kstarbound.world.entities.api import com.google.gson.JsonArray import com.google.gson.JsonElement +import com.google.gson.JsonNull +import ru.dbotthepony.kstarbound.lua.LuaThread interface ScriptedEntity { + val lua: LuaThread + // Call a script function directly with the given arguments, should return // nothing only on failure. - fun callScript(fnName: String, arguments: JsonArray): JsonElement + fun callScript(fnName: String, arguments: JsonArray): JsonElement { + return lua.invokeGlobal(fnName, 1, { + ensureExtraCapacity(arguments.size() + 4) + + for (argument in arguments) { + push(argument) + } + + arguments.size() + }, { getJson() ?: JsonNull.INSTANCE }).orElse(JsonNull.INSTANCE) + } // Execute the given code directly in the underlying context, return nothing // on failure. - fun evalScript(code: String): Array + fun evalScript(code: String): JsonElement { + return lua.eval(code) + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt index fc8700ee..f0ed53bc 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt @@ -16,7 +16,7 @@ import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.json.builder.IStringSerializable -import ru.dbotthepony.kstarbound.lua.LuaEnvironment +import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.math.vector.Vector2d @@ -101,7 +101,7 @@ class PlayerEntity() : HumanoidActorEntity() { val newChatMessage = networkGroup.upstream.add(networkedEventCounter()) var emote by networkGroup.upstream.add(networkedEnumExtraStupid(HumanoidEmote.IDLE)) - override val lua: LuaEnvironment + override val lua: LuaThread get() = TODO("Not yet implemented") override val emoteCooldownTimer: GameTimer = GameTimer() override val danceTimer: GameTimer? diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt index dc1c3124..2f034c21 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt @@ -12,29 +12,20 @@ import com.google.gson.TypeAdapter import com.google.gson.reflect.TypeToken import it.unimi.dsi.fastutil.objects.ObjectArrayList import org.apache.logging.log4j.LogManager -import org.classdump.luna.ByteString -import org.classdump.luna.Table import ru.dbotthepony.kommons.gson.JsonArrayCollector import ru.dbotthepony.kommons.gson.contains -import ru.dbotthepony.kommons.math.RGBAColor -import ru.dbotthepony.kstarbound.math.vector.Vector2i -import ru.dbotthepony.kstarbound.Registries -import ru.dbotthepony.kstarbound.Registry -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition -import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.guava.immutableSet -import ru.dbotthepony.kstarbound.io.StreamCodec -import ru.dbotthepony.kstarbound.io.map import ru.dbotthepony.kommons.io.writeBinaryString -import ru.dbotthepony.kstarbound.math.AABB +import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kstarbound.Globals +import ru.dbotthepony.kstarbound.Registries +import ru.dbotthepony.kstarbound.Registry +import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.DamageNotification -import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.defs.DamageSource import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.HitType @@ -42,15 +33,30 @@ import ru.dbotthepony.kstarbound.defs.InteractAction import ru.dbotthepony.kstarbound.defs.InteractRequest import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor +import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition +import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation import ru.dbotthepony.kstarbound.defs.`object`.ObjectType import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor import ru.dbotthepony.kstarbound.defs.tile.TileDamage import ru.dbotthepony.kstarbound.io.RGBACodec +import ru.dbotthepony.kstarbound.io.StreamCodec import ru.dbotthepony.kstarbound.io.Vector2iCodec +import ru.dbotthepony.kstarbound.io.map import ru.dbotthepony.kstarbound.json.JsonPath import ru.dbotthepony.kstarbound.json.jsonArrayOf import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.stream +import ru.dbotthepony.kstarbound.json.writeJsonElement +import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent +import ru.dbotthepony.kstarbound.lua.LuaThread +import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent +import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings +import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings +import ru.dbotthepony.kstarbound.lua.setTableValue +import ru.dbotthepony.kstarbound.math.AABB +import ru.dbotthepony.kstarbound.math.vector.Vector2d +import ru.dbotthepony.kstarbound.math.vector.Vector2i +import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec import ru.dbotthepony.kstarbound.network.syncher.JsonElementCodec import ru.dbotthepony.kstarbound.network.syncher.NetworkedList @@ -64,21 +70,6 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedFloat import ru.dbotthepony.kstarbound.network.syncher.networkedJsonElement import ru.dbotthepony.kstarbound.network.syncher.networkedPointer import ru.dbotthepony.kstarbound.network.syncher.networkedString -import ru.dbotthepony.kstarbound.json.writeJsonElement -import ru.dbotthepony.kstarbound.lua.LuaEnvironment -import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent -import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent -import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings -import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings -import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.get -import ru.dbotthepony.kstarbound.lua.set -import ru.dbotthepony.kstarbound.lua.tableMapOf -import ru.dbotthepony.kstarbound.lua.tableOf -import ru.dbotthepony.kstarbound.lua.toJson -import ru.dbotthepony.kstarbound.lua.toJsonFromLua -import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket -import ru.dbotthepony.kstarbound.server.world.LegacyWireProcessor import ru.dbotthepony.kstarbound.util.ManualLazy import ru.dbotthepony.kstarbound.util.asStringOrNull import ru.dbotthepony.kstarbound.util.random.random @@ -92,19 +83,20 @@ import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity import ru.dbotthepony.kstarbound.world.physics.Poly import java.io.DataOutputStream -import java.util.Collections -import java.util.HashMap +import java.util.* import java.util.random.RandomGenerator import kotlin.math.min +import kotlin.properties.Delegates open class WorldObject(val config: Registry.Entry) : TileEntity(), ScriptedEntity, InteractiveEntity { + private var scriptStorage = JsonObject() + override fun deserialize(data: JsonObject) { super.deserialize(data) direction = data.get("direction", directions) { Direction.LEFT } orientationIndex = data.get("orientationIndex", -1).toLong() isInteractive = data.get("interactive", false) - - lua.globals["storage"] = lua.from(data.get("scriptStorage") { JsonObject() }) + scriptStorage = data.get("scriptStorage") { JsonObject() } loadParameters(data.get("parameters") { JsonObject() }) @@ -159,10 +151,11 @@ open class WorldObject(val config: Registry.Entry) : TileEntit data["inputWireNodes"] = inputNodes.stream().map { it.serialize() }.collect(JsonArrayCollector) data["outputWireNodes"] = outputNodes.stream().map { it.serialize() }.collect(JsonArrayCollector) - val scriptStorage = lua.globals["storage"] + lua.loadGlobal("storage") + val scriptStorage = lua.popJson() - if (scriptStorage != null && scriptStorage is Table) { - data["scriptStorage"] = scriptStorage.toJson(true) + if (scriptStorage != null && scriptStorage is JsonObject) { + data["scriptStorage"] = scriptStorage } uniqueID.get()?.let { @@ -313,13 +306,13 @@ open class WorldObject(val config: Registry.Entry) : TileEntit fun addConnection(connection: WireConnection) { if (connection !in connectionsInternal) { connectionsInternal.add(connection.copy()) - lua.invokeGlobal("onNodeConnectionChange") + lua.invokeGlobal("onNodeConnectionChange", 0) } } fun removeConnection(connection: WireConnection) { if (connectionsInternal.remove(connection)) { - lua.invokeGlobal("onNodeConnectionChange") + lua.invokeGlobal("onNodeConnectionChange", 0) } } @@ -332,20 +325,20 @@ open class WorldObject(val config: Registry.Entry) : TileEntit val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index } if (any == true) { - otherEntity!!.lua.invokeGlobal("onNodeConnectionChange") + otherEntity!!.lua.invokeGlobal("onNodeConnectionChange", 0) } any == true } if (any) - lua.invokeGlobal("onNodeConnectionChange") + lua.invokeGlobal("onNodeConnectionChange", 0) } } fun removeConnectionsTo(pos: Vector2i) { if (connectionsInternal.removeIf { it.entityLocation == pos }) { - lua.invokeGlobal("onNodeConnectionChange") + lua.invokeGlobal("onNodeConnectionChange", 0) } } @@ -431,13 +424,12 @@ open class WorldObject(val config: Registry.Entry) : TileEntit networkGroup.upstream.add(animator.networkGroup) } - val lua = LuaEnvironment() - val luaUpdate = LuaUpdateComponent(lua) - val luaMessageHandler = LuaMessageHandlerComponent(lua) { toString() } - - init { - lua.globals["storage"] = lua.newTable() - } + final override var lua by Delegates.notNull() + private set + var luaUpdate by Delegates.notNull() + private set + var luaMessageHandler by Delegates.notNull() + private set val unbreakable by ManualLazy { lookupProperty("unbreakable") { JsonPrimitive(false) }.asBoolean @@ -553,14 +545,23 @@ open class WorldObject(val config: Registry.Entry) : TileEntit animator.setPartTag(k, "partImage", v.asString) } + lua = LuaThread() + lua.push(scriptStorage) + lua.storeGlobal("storage") + luaUpdate = LuaUpdateComponent(lua, this) + luaMessageHandler = LuaMessageHandlerComponent(lua) { toString() } + updateMaterialSpacesNow() provideEntityBindings(this, lua) provideAnimatorBindings(animator, lua) lua.attach(config.value.scripts) luaUpdate.stepCount = lookupProperty(JsonPath("scriptDelta")) { JsonPrimitive(5) }.asDouble - lua.init() + lua.initScripts() } + // free up memory + scriptStorage = JsonObject() + // as original code puts it: // Don't animate the initial state when first spawned IF you're dumb, which by default // you would be, and don't know how to use transition and static states properly. Someday @@ -593,21 +594,25 @@ open class WorldObject(val config: Registry.Entry) : TileEntit override fun interact(request: InteractRequest): InteractAction { val diff = world.geometry.diff(request.sourcePos, position) - val result = lua.invokeGlobal("onInteraction", lua.newTable().apply { - this["source"] = lua.tableOf(diff.x, diff.y) - this["sourceId"] = request.source - }) + val result = lua.invokeGlobal("onInteraction", 1, { + pushTable(hashSize = 2) - if (result.isNotEmpty()) { - if (result[0] == null) + setTableValue("source", diff) + setTableValue("sourceId", request.source) + + 1 + }, { getJson() ?: JsonNull.INSTANCE }) + + if (result.isPresent) { + if (result.value.isJsonNull) return InteractAction.NONE - else if (result[0] is ByteString) { - val decoded = (result[0] as ByteString).decode() + else if (result.value is JsonPrimitive) { + val decoded = result.value.asString return InteractAction(InteractAction.Type.entries.firstOrNull { it.jsonName == decoded } ?: throw NoSuchElementException("Unknown interaction action type $decoded!"), entityID) } else { - val data = result[0] as Table - val decoded = (data[1L] as ByteString).decode() - return InteractAction(InteractAction.Type.entries.firstOrNull { it.jsonName == decoded } ?: throw NoSuchElementException("Unknown interaction action type $decoded!"), entityID, toJsonFromLua(data[2L])) + val data = result.value as JsonArray + val decoded = data[0].asString + return InteractAction(InteractAction.Type.entries.firstOrNull { it.jsonName == decoded } ?: throw NoSuchElementException("Unknown interaction action type $decoded!"), entityID, if (data.size() >= 2) data[1] else JsonNull.INSTANCE) } } @@ -670,7 +675,7 @@ open class WorldObject(val config: Registry.Entry) : TileEntit // break connection if other entity got removed if (connection.otherEntity?.removalReason?.removal == true) { itr.remove() - lua.invokeGlobal("onNodeConnectionChange") + lua.invokeGlobal("onNodeConnectionChange", 0) continue } @@ -686,7 +691,7 @@ open class WorldObject(val config: Registry.Entry) : TileEntit // break connection if we point at invalid node if (otherNode == null) { itr.remove() - lua.invokeGlobal("onNodeConnectionChange") + lua.invokeGlobal("onNodeConnectionChange", 0) } else { newState = newState!! || otherNode.state } @@ -701,7 +706,10 @@ open class WorldObject(val config: Registry.Entry) : TileEntit // otherwise, keep current node state if (newState != null && node.state != newState) { node.state = newState - lua.invokeGlobal("onInputNodeChange", lua.tableMapOf(LegacyWireProcessor.NODE_KEY to i.toLong(), LegacyWireProcessor.LEVEL_KEY to newState)) + lua.pushTable(hashSize = 2) + lua.setTableValue("node", i) + lua.setTableValue("level", newState) + lua.invokeGlobal("onInputNodeChange", 1) } } } @@ -734,7 +742,7 @@ open class WorldObject(val config: Registry.Entry) : TileEntit override fun onRemove(world: World<*, *>, reason: RemovalReason) { super.onRemove(world, reason) - val doSmash = config.value.smashable && (health <= 0.0 || lookupProperty("smashOnBreak") { JsonPrimitive(config.value.smashOnBreak) }.asBoolean) + val doSmash = reason.dying && config.value.smashable && (health <= 0.0 || lookupProperty("smashOnBreak") { JsonPrimitive(config.value.smashOnBreak) }.asBoolean) fun spawnRandomItems(poolName: String, optionsName: String, seedName: String): Boolean { val dropPool = lookupProperty(poolName) { JsonPrimitive("") }.asString @@ -774,7 +782,8 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } if (!isRemote && reason.dying) { - lua.invokeGlobal("die", health <= 0.0) + lua.push(health <= 0.0) + lua.invokeGlobal("die", 1) try { if (doSmash) { @@ -791,7 +800,8 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } parameters.remove("owner") - parameters["scriptStorage"] = (lua.globals["storage"] as Table).toJson(true) + lua.loadGlobal("storage") + parameters["scriptStorage"] = lua.popJson() } val entity = ItemDropEntity(ItemDescriptor(config.key, 1L, parameters)) @@ -805,6 +815,14 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } } + override fun uninit(world: World<*, *>) { + super.uninit(world) + + if (!isRemote) { + lua.close() + } + } + override fun damageTileEntity(damageSpaces: List, source: Vector2d, damage: TileDamage): Boolean { if (unbreakable) return false @@ -864,15 +882,6 @@ open class WorldObject(val config: Registry.Entry) : TileEntit return luaMessageHandler.handle(message, connectionID == connection, arguments) } - override fun callScript(fnName: String, arguments: JsonArray): JsonElement { - //return lua.invokeGlobal(fnName, *arguments) - TODO() - } - - override fun evalScript(code: String): Array { - return lua.eval(code) - } - init { isInteractive = !interactAction.isJsonNull }