diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt index c2b184a0..1a36be37 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt @@ -52,14 +52,14 @@ fun LuaThread.getLine2d(stackIndex: Int = -1): Line2d? { push(1) loadTableValue(abs) - val x = getVector2d(abs + 1) + val x = getVector2d() pop() x ?: return null push(2) loadTableValue(abs) - val y = getVector2d(abs + 1) + val y = getVector2d() pop() y ?: return null @@ -90,14 +90,14 @@ fun LuaThread.getVector2d(stackIndex: Int = -1): Vector2d? { push(1) loadTableValue(abs) - val x = getDouble(abs + 1) + val x = getDouble() pop() x ?: return null push(2) loadTableValue(abs) - val y = getDouble(abs + 1) + val y = getDouble() pop() y ?: return null @@ -128,14 +128,14 @@ fun LuaThread.getVector2f(stackIndex: Int = -1): Vector2f? { push(1) loadTableValue(abs) - val x = getFloat(abs + 1) + val x = getFloat() pop() x ?: return null push(2) loadTableValue(abs) - val y = getFloat(abs + 1) + val y = getFloat() pop() y ?: return null @@ -168,9 +168,9 @@ fun LuaThread.getVector2iOrAABB(stackIndex: Int = -1): Either? { pop() if (type == LuaType.NUMBER) { - return Either.right(getAABB(stackIndex) ?: return null) + return Either.right(getAABB() ?: return null) } else { - return Either.left(getVector2i(stackIndex) ?: return null) + return Either.left(getVector2i() ?: return null) } } @@ -223,14 +223,14 @@ fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? { push(1) loadTableValue(abs) - val x = getLong(abs + 1) + val x = getLong() pop() x ?: return null push(2) loadTableValue(abs) - val y = getLong(abs + 1) + val y = getLong() pop() y ?: return null @@ -262,28 +262,28 @@ fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? { push(1) loadTableValue(abs) - val x = getLong(abs + 1) + val x = getLong() pop() x ?: return null push(2) loadTableValue(abs) - val y = getLong(abs + 1) + val y = getLong() pop() y ?: return null push(3) loadTableValue(abs) - val z = getLong(abs + 1) + val z = getLong() pop() z ?: return null push(4) loadTableValue(abs) - val w = getLong(abs + 1) ?: 255L + val w = getLong() ?: 255L pop() return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) @@ -313,28 +313,28 @@ fun LuaThread.getAABB(stackIndex: Int = -1): AABB? { push(1) loadTableValue(abs) - val x = getDouble(abs + 1) + val x = getDouble() pop() x ?: return null push(2) loadTableValue(abs) - val y = getDouble(abs + 1) + val y = getDouble() pop() y ?: return null push(3) loadTableValue(abs) - val z = getDouble(abs + 1) + val z = getDouble() pop() z ?: return null push(4) loadTableValue(abs) - val w = getDouble(abs + 1) + val w = getDouble() pop() w ?: return null @@ -343,10 +343,10 @@ fun LuaThread.getAABB(stackIndex: Int = -1): AABB? { fun LuaThread.ArgStack.nextAABB(position: Int = this.position++): AABB { if (position !in 1 ..this.top) - throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got nil") + throw IllegalArgumentException("bad argument #$position: AABB expected, got nil") return lua.getAABB(position) - ?: throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: AABB expected, got ${lua.typeAt(position)}") } fun LuaThread.ArgStack.nextOptionalAABB(position: Int = this.position++): AABB? { @@ -365,28 +365,28 @@ fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? { push(1) loadTableValue(abs) - val x = getLong(abs + 1) + val x = getLong() pop() x ?: return null push(2) loadTableValue(abs) - val y = getLong(abs + 1) + val y = getLong() pop() y ?: return null push(3) loadTableValue(abs) - val z = getLong(abs + 1) + val z = getLong() pop() z ?: return null push(4) loadTableValue(abs) - val w = getLong(abs + 1) + val w = getLong() pop() w ?: return null diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt index e2993cf4..2426a75a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt @@ -5,11 +5,12 @@ 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.lang.ref.Cleaner.Cleanable import java.util.concurrent.ConcurrentLinkedQueue import java.util.random.RandomGenerator import kotlin.properties.Delegates -class LuaSharedState(val handlesThread: LuaThread) : Closeable { +class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleanable) : Closeable { private val pendingFree = ConcurrentLinkedQueue() private val freeHandles = IntAVLTreeSet() private var nextHandle = 0 @@ -25,10 +26,15 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable { var isValid = true private set + fun ensureValid() { + check(isValid) { "Tried to use NULL LuaState!" } + } + override fun close() { if (!isValid) return isValid = false namedHandles.clear() + cleanable.clean() } fun initializeHandles(mainThread: LuaThread) { @@ -51,7 +57,7 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable { } fun cleanup() { - check(isValid) { "Shared state is no longer valid" } + ensureValid() if (handlesInUse == 0) return var handle = pendingFree.poll() @@ -67,7 +73,7 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable { } fun allocateHandle(name: Any?): LuaHandle { - check(isValid) { "Shared state is no longer valid" } + ensureValid() require(name == null || name !in namedHandles) { "Named handle '$name' already exists" } handlesInUse++ @@ -93,12 +99,12 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable { } fun getNamedHandle(key: Any): LuaHandle { - check(isValid) { "Shared state is no longer valid" } + ensureValid() return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key") } fun findNamedHandle(key: Any): LuaHandle? { - check(isValid) { "Shared state is no longer valid" } + ensureValid() 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 3342dc68..49d07818 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt @@ -56,18 +56,18 @@ class LuaThread private constructor( CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false) ) - this.cleanable = Cleaner.Cleanable { - LuaJNR.INSTANCE.lua_close(pointer) - panic.dispose() - } - panic.setAutoRelease(false) LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address) val handles = LuaJNR.INSTANCE.lua_newthread(pointer) - storeRef(LUA_REGISTRYINDEX) + LuaJNR.INSTANCE.luaL_ref(pointer, LUA_REGISTRYINDEX) val handlesThread = LuaThread(handles, stringInterner) - sharedState = LuaSharedState(handlesThread) + + sharedState = LuaSharedState(handlesThread, { + LuaJNR.INSTANCE.lua_close(pointer) + panic.dispose() + }) + sharedState.handlesThread.sharedState = sharedState push("__nils") @@ -140,13 +140,11 @@ class LuaThread private constructor( } override fun close() { - if (cleanable != null) { - cleanable!!.clean() - sharedState.close() - } + sharedState.close() } val stackTop: Int get() { + sharedState.ensureValid() val value = LuaJNR.INSTANCE.lua_gettop(this.pointer) check(value >= 0) { "Invalid stack top $value" } return value @@ -156,6 +154,7 @@ class LuaThread private constructor( * Converts the acceptable index idx into an equivalent absolute index (that is, one that does not depend on the stack size). */ fun absStackIndex(index: Int): Int { + sharedState.ensureValid() if (index >= 0) return index @@ -173,6 +172,7 @@ class LuaThread private constructor( } fun load(code: String, chunkName: String = "@main chunk") { + sharedState.ensureValid() val bytes = code.toByteArray(charset = Charsets.UTF_8) val buf = ByteBuffer.allocateDirect(bytes.size) buf.order(ByteOrder.nativeOrder()) @@ -319,6 +319,7 @@ class LuaThread private constructor( private var initCalled = false fun initScripts(callInit: Boolean = true): Boolean { + sharedState.ensureValid() check(!initCalled) { "Already initialized scripts!" } initCalled = true @@ -362,10 +363,12 @@ class LuaThread private constructor( } fun attach(script: AssetPath) { + sharedState.ensureValid() attach(script.fullPath) } fun attach(script: String) { + sharedState.ensureValid() if (initCalled) { // minor hiccups during unpopulated script cache should be tolerable load(Starbound.readLuaScript(script).join(), "@$script") @@ -392,6 +395,7 @@ class LuaThread private constructor( } private fun getStringRaw(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? { + sharedState.ensureValid() require(limit <= Int.MAX_VALUE) { "Can't allocate string bigger than ${Int.MAX_VALUE} characters" } val stack = MemoryStack.stackPush() val status = stack.mallocLong(1) @@ -410,19 +414,19 @@ class LuaThread private constructor( return readBytes.toString(charset = Charsets.UTF_8) } - fun isCFunction(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_iscfunction(this.pointer, this.absStackIndex(stackIndex)) > 0 - fun isFunction(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.FUNCTION - fun isInteger(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isinteger(this.pointer, this.absStackIndex(stackIndex)) > 0 - fun isLightUserdata(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.LIGHTUSERDATA - fun isNil(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.NIL - fun isNone(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.NONE - fun isNoneOrNil(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex).let { it == LuaType.NIL || it == LuaType.NONE } - fun isNumber(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isnumber(this.pointer, this.absStackIndex(stackIndex)) > 0 - fun isString(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isstring(this.pointer, this.absStackIndex(stackIndex)) > 0 - fun isTable(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.TABLE - fun isThread(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.THREAD - fun isUserdata(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isuserdata(this.pointer, this.absStackIndex(stackIndex)) > 0 - fun isBoolean(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.BOOLEAN + fun isCFunction(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return LuaJNR.INSTANCE.lua_iscfunction(this.pointer, this.absStackIndex(stackIndex)) > 0 } + fun isFunction(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.FUNCTION } + fun isInteger(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return LuaJNR.INSTANCE.lua_isinteger(this.pointer, this.absStackIndex(stackIndex)) > 0 } + fun isLightUserdata(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.LIGHTUSERDATA } + fun isNil(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.NIL } + fun isNone(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.NONE } + fun isNoneOrNil(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex).let { it == LuaType.NIL || it == LuaType.NONE } } + fun isNumber(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return LuaJNR.INSTANCE.lua_isnumber(this.pointer, this.absStackIndex(stackIndex)) > 0 } + fun isString(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return LuaJNR.INSTANCE.lua_isstring(this.pointer, this.absStackIndex(stackIndex)) > 0 } + fun isTable(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.TABLE } + fun isThread(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.THREAD } + fun isUserdata(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return LuaJNR.INSTANCE.lua_isuserdata(this.pointer, this.absStackIndex(stackIndex)) > 0 } + fun isBoolean(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.BOOLEAN } fun getBoolean(stackIndex: Int = -1): Boolean? { if (!this.isBoolean(stackIndex)) @@ -514,6 +518,7 @@ class LuaThread private constructor( } fun typeAt(stackIndex: Int = -1): LuaType { + sharedState.ensureValid() return LuaType.valueOf(LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex)) } @@ -597,7 +602,7 @@ class LuaThread private constructor( push() val top = this.stackTop - while (LuaJNR.INSTANCE.lua_next(this.pointer, top - 1) != 0) { + while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { val key = this.getJson(top, limit = limit) val value = this.getJson(top + 1, limit = limit) @@ -690,6 +695,7 @@ class LuaThread private constructor( } fun getObject(stackIndex: Int = -1): Any? { + sharedState.ensureValid() return LuaJNI.lua_tojobject(pointer.address(), stackIndex) } @@ -785,6 +791,7 @@ class LuaThread private constructor( } fun loadTableValue(stackIndex: Int = -2): LuaType { + sharedState.ensureValid() return LuaType.valueOf(LuaJNR.INSTANCE.lua_gettable(this.pointer, stackIndex)) } @@ -850,16 +857,19 @@ class LuaThread private constructor( } fun pop(amount: Int = 1) { + sharedState.ensureValid() require(amount >= 0) { "Invalid amount of values to pop: $amount" } if (amount == 0) return LuaJNR.INSTANCE.lua_settop(this.pointer, -amount - 1) } fun storeGlobal(name: String) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_setglobal(this.pointer, name) } fun loadGlobal(name: String): LuaType { + sharedState.ensureValid() return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name)) } @@ -1163,6 +1173,7 @@ class LuaThread private constructor( } fun push(function: Fn, performanceCritical: Boolean) { + sharedState.ensureValid() LuaJNI.lua_pushcclosure(pointer.address()) { closure(it, function, performanceCritical) } @@ -1213,6 +1224,7 @@ class LuaThread private constructor( } fun ensureExtraCapacity(maxSize: Int): Boolean { + sharedState.ensureValid() return LuaJNR.INSTANCE.lua_checkstack(pointer, maxSize) } @@ -1227,18 +1239,22 @@ class LuaThread private constructor( } fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount) } fun push() { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_pushnil(this.pointer) } fun push(value: Long) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value) } fun push(value: Long?) { + sharedState.ensureValid() if (value == null) { LuaJNR.INSTANCE.lua_pushnil(pointer) } else { @@ -1247,18 +1263,22 @@ class LuaThread private constructor( } fun push(value: Double) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value) } fun push(value: Float) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value.toDouble()) } fun push(value: Boolean) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0) } fun push(value: Double?) { + sharedState.ensureValid() if (value == null) { LuaJNR.INSTANCE.lua_pushnil(pointer) } else { @@ -1267,6 +1287,7 @@ class LuaThread private constructor( } fun push(value: Float?) { + sharedState.ensureValid() if (value == null) { LuaJNR.INSTANCE.lua_pushnil(pointer) } else { @@ -1275,6 +1296,7 @@ class LuaThread private constructor( } fun push(value: Boolean?) { + sharedState.ensureValid() if (value == null) { LuaJNR.INSTANCE.lua_pushnil(pointer) } else { @@ -1283,6 +1305,7 @@ class LuaThread private constructor( } fun push(value: String?) { + sharedState.ensureValid() if (value == null) { push() } else { @@ -1291,10 +1314,12 @@ class LuaThread private constructor( } fun push(value: LuaHandle) { + sharedState.ensureValid() value.push(this) } fun pushObject(value: Any) { + sharedState.ensureValid() LuaJNI.lua_pushjobject(pointer.address(), value) } @@ -1334,6 +1359,7 @@ class LuaThread private constructor( } fun copy(fromIndex: Int, toIndex: Int) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex) } @@ -1352,18 +1378,22 @@ class LuaThread private constructor( } fun setTop(topIndex: Int) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_settop(pointer, topIndex) } fun storeRef(tableIndex: Int) { + sharedState.ensureValid() LuaJNR.INSTANCE.luaL_ref(pointer, tableIndex) } fun pushTable(arraySize: Int = 0, hashSize: Int = 0) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_createtable(pointer, arraySize, hashSize) } fun setTableValue(stackIndex: Int = -3) { + sharedState.ensureValid() LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt index 9ca33bfa..1dd32b56 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt @@ -225,6 +225,26 @@ sealed class LegacyWorldStorage() : WorldStorage() { } override fun saveEntities(pos: ChunkPos, entities: Collection) { + // since entities access their Lua state on save, we must collect everything before "unloading" them from memory + val uniques = HashMap() + val entityData = ArrayList() + + for (entity in entities) { + Starbound.legacyStoreJson { + val stream = FastByteArrayOutputStream() + val data = JsonObject() + entity.serialize(data) + VersionRegistry.make(entity.type.storeName, data).write(DataOutputStream(stream)) + entityData.add(stream) + } + + val uniqueID = entity.uniqueID.get() + + if (uniqueID != null) { + uniques[uniqueID] = entity.position + } + } + scope.launch { val chunkX = pos.x val chunkY = pos.y @@ -236,23 +256,11 @@ sealed class LegacyWorldStorage() : WorldStorage() { val streamEntities = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buffEntities))) val streamUniques = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buffUniques))) - val uniques = HashMap() - try { - streamEntities.writeVarInt(entities.size) + streamEntities.writeVarInt(entityData.size) - for (entity in entities) { - Starbound.legacyStoreJson { - val data = JsonObject() - entity.serialize(data) - VersionRegistry.make(entity.type.storeName, data).write(streamEntities) - } - - val uniqueID = entity.uniqueID.get() - - if (uniqueID != null) { - uniques[uniqueID] = entity.position - } + for (data in entityData) { + streamEntities.write(data.array, 0, data.length) } streamEntities.close() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeLocalWorldStorage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeLocalWorldStorage.kt index bbab3612..919bb820 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeLocalWorldStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeLocalWorldStorage.kt @@ -264,31 +264,37 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() { """.trimIndent()) override fun saveEntities(pos: ChunkPos, entities: Collection) { + // since entities access their Lua state on save, we must collect everything before "unloading" them from memory + val storeData = JsonArray() + val uniques = ArrayList() + + for (entity in entities) { + Starbound.storeJson { + val data = JsonObject() + entity.serialize(data) + storeData.add(VersionRegistry.make(entity.type.storeName, data).toJson()) + + if (entity.uniqueID.get() != null) { + uniques.add { + writeUniqueEntity.setString(1, entity.uniqueID.get()) + writeUniqueEntity.setInt(2, pos.x) + writeUniqueEntity.setInt(3, pos.y) + writeUniqueEntity.setDouble(4, entity.position.x) + writeUniqueEntity.setDouble(5, entity.position.y) + + writeUniqueEntity.execute() + } + } + } + } + executor.execute { entitiesSavepoint.execute { clearUniqueEntities.setInt(1, pos.x) clearUniqueEntities.setInt(2, pos.y) clearUniqueEntities.execute() - val storeData = JsonArray() - - for (entity in entities) { - Starbound.storeJson { - val data = JsonObject() - entity.serialize(data) - storeData.add(VersionRegistry.make(entity.type.storeName, data).toJson()) - - if (entity.uniqueID.get() != null) { - writeUniqueEntity.setString(1, entity.uniqueID.get()) - writeUniqueEntity.setInt(2, pos.x) - writeUniqueEntity.setInt(3, pos.y) - writeUniqueEntity.setDouble(4, entity.position.x) - writeUniqueEntity.setDouble(5, entity.position.y) - - writeUniqueEntity.execute() - } - } - } + uniques.forEach { it.run() } writeEntities.setInt(1, pos.x) writeEntities.setInt(2, pos.y) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt index b066c59e..1ebad4d8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt @@ -231,7 +231,11 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk { check(!world.entities.containsKey(entityID)) { "Duplicate entity ID: $entityID" } innerWorld = world - uniqueID.get()?.let { - check(it !in world.uniqueEntities) { "Duplicate unique entity ID: $it" } - world.uniqueEntities[it] = this + try { + uniqueID.get()?.let { + check(it !in world.uniqueEntities) { "Duplicate unique entity ID: $it" } + world.uniqueEntities[it] = this + } + } catch (err: Throwable) { + if (world is ClientWorld) { + world.client.activeConnection?.freeEntityID(entityID) + } + + innerWorld = null + throw err } world.entities[entityID] = this world.entityList.add(this) spatialEntry = world.entityIndex.Entry(this) - onJoinWorld(world) + + try { + onJoinWorld(world) + } catch (err: Throwable) { + try { + uninit(world) + } catch (err2: Exception) { + // should be harmless + err.addSuppressed(err2) + } + + world.entities.remove(entityID) + world.entityList.remove(this) + spatialEntry!!.remove() + spatialEntry = null + + uniqueID.get()?.let { + world.uniqueEntities.remove(it) + } + + if (world is ClientWorld) { + world.client.activeConnection?.freeEntityID(entityID) + } + + innerWorld = null + throw err + } if (visibleToRemotes) { if (world is ClientWorld && !isRemote) { 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 88f6293e..d8770a6b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt @@ -8,6 +8,7 @@ import com.google.gson.JsonElement import com.google.gson.JsonNull import com.google.gson.JsonObject import it.unimi.dsi.fastutil.objects.ObjectArraySet +import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.util.Either @@ -404,9 +405,13 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE return } } else { - luaUpdate.update(delta) { - luaMovement.clearControlsIfNeeded() - forceRegions.clear() + try { + luaUpdate.update(delta) { + luaMovement.clearControlsIfNeeded() + forceRegions.clear() + } + } catch (err: Exception) { + LOGGER.error("Exception while ticking $this", err) } if (shouldDie) { @@ -429,4 +434,8 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? { return luaMessages.handle(message, connection == connectionID, arguments) ?: statusController.handleMessage(message, connection == connectionID, arguments) } + + companion object { + private val LOGGER = LogManager.getLogger() + } } 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 ceb9b845..a667c522 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StatusController.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/StatusController.kt @@ -151,6 +151,12 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf // provideStatusControllerBindings(this, lua) // provided through provideEntityBindings provideEntityBindings(entity, lua) + if (animator != null) + // FIXME: effect animator's animator is not set in stone because of legacy protocol + // god damn it + // But at least it shouldn't change in this context + provideAnimatorBindings(animator.animator, lua) + // TODO: Once we have brand new object-oriented Lua API, expose proper entity bindings here // TODO: Expose world bindings lua.initScripts() @@ -261,13 +267,6 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf } else { animator = EffectAnimator(KOptional(config.primaryAnimationConfig.fullPath)) animatorID = effectAnimators.add(animator) - - // FIXME: effect animator's animator is not set in stone because of legacy protocol - // god damn it - // But at least it shouldn't change in this context - if (!entity.isRemote) { - provideAnimatorBindings(animator.animator, lua) - } } } diff --git a/src/main/resources/scripts/behavior.lua b/src/main/resources/scripts/behavior.lua index 38b7376a..2ae2c9f6 100644 --- a/src/main/resources/scripts/behavior.lua +++ b/src/main/resources/scripts/behavior.lua @@ -411,7 +411,7 @@ function seqNode:run(delta, blackboard) self.calls = self.calls + 1 local size = self.size local isSelector = self.isSelector -` + while self.index <= size do local child = self.children[self.index] local status = runAndReset(child, delta, blackboard) diff --git a/src/main/resources/scripts/global.lua b/src/main/resources/scripts/global.lua index b29d4897..a7a2be90 100644 --- a/src/main/resources/scripts/global.lua +++ b/src/main/resources/scripts/global.lua @@ -200,13 +200,13 @@ do error('require: script path must be absolute: ' .. path) end - if loadedScripts[path] then return unpack(loadedScripts[path]) end + if loadedScripts[path] then return table.unpack(loadedScripts[path]) end local fn = __require(path) if fn then local result = {fn(...)} loadedScripts[path] = result - return unpack(result) + return table.unpack(result) else print('Failed to require Lua script ' .. path) loadedScripts[path] = {} @@ -235,7 +235,7 @@ do end low = 1 - high = tA + high = a if high % 1.0 ~= 0.0 then error('bad argument #1 to math.random: integer expected, got double', 2) @@ -249,8 +249,8 @@ do error('bad argument #2 to math.random: number expected, got ' .. tB, 2) end - low = tA - high = tB + low = a + high = b if low % 1.0 ~= 0.0 then error('bad argument #1 to math.random: integer expected, got double', 2) @@ -385,20 +385,4 @@ function mergeJson(base, with) end end -string.__index = string - -do - local sub = string.sub - - function string:__index(key) - if type(key) == 'number' then - return sub(self, key, key) - else - return string[key] - end - end -end - -setmetatable('', string) - diff --git a/src/main/resources/scripts/world.lua b/src/main/resources/scripts/world.lua index 679330a0..ebf25d2c 100644 --- a/src/main/resources/scripts/world.lua +++ b/src/main/resources/scripts/world.lua @@ -175,7 +175,7 @@ for fnName, implName in pairs(regularQueryFunctions) do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) else -- point + radius @@ -190,7 +190,7 @@ for fnName, implName in pairs(regularQueryFunctions) do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -218,7 +218,7 @@ for fnName, implName in pairs(regularQueryFunctions) do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -244,7 +244,7 @@ for fnName, implName in pairs(regularQueryFunctions) do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -284,7 +284,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) else -- point + radius @@ -300,7 +300,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -333,7 +333,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -364,7 +364,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -423,7 +423,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) else -- point + radius @@ -439,7 +439,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -468,7 +468,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end @@ -495,7 +495,7 @@ do options.callScript, options.callScriptArgs or {}, options.callScriptResult, - order(options.order, fullName), + order(options.order, fullName) ) end end