Make it not outright crash with 0xC0000005 or 0xC00000FD

This commit is contained in:
DBotThePony 2024-12-28 22:26:30 +07:00
parent 9a958ecccb
commit 49d6cb0d89
Signed by: DBot
GPG Key ID: DCC23B5715498507
13 changed files with 219 additions and 139 deletions

View File

@ -52,14 +52,14 @@ fun LuaThread.getLine2d(stackIndex: Int = -1): Line2d? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getVector2d(abs + 1) val x = getVector2d()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getVector2d(abs + 1) val y = getVector2d()
pop() pop()
y ?: return null y ?: return null
@ -90,14 +90,14 @@ fun LuaThread.getVector2d(stackIndex: Int = -1): Vector2d? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getDouble(abs + 1) val x = getDouble()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getDouble(abs + 1) val y = getDouble()
pop() pop()
y ?: return null y ?: return null
@ -128,14 +128,14 @@ fun LuaThread.getVector2f(stackIndex: Int = -1): Vector2f? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getFloat(abs + 1) val x = getFloat()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getFloat(abs + 1) val y = getFloat()
pop() pop()
y ?: return null y ?: return null
@ -168,9 +168,9 @@ fun LuaThread.getVector2iOrAABB(stackIndex: Int = -1): Either<Vector2i, AABB>? {
pop() pop()
if (type == LuaType.NUMBER) { if (type == LuaType.NUMBER) {
return Either.right(getAABB(stackIndex) ?: return null) return Either.right(getAABB() ?: return null)
} else { } 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) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getLong(abs + 1) val x = getLong()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getLong(abs + 1) val y = getLong()
pop() pop()
y ?: return null y ?: return null
@ -262,28 +262,28 @@ fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getLong(abs + 1) val x = getLong()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getLong(abs + 1) val y = getLong()
pop() pop()
y ?: return null y ?: return null
push(3) push(3)
loadTableValue(abs) loadTableValue(abs)
val z = getLong(abs + 1) val z = getLong()
pop() pop()
z ?: return null z ?: return null
push(4) push(4)
loadTableValue(abs) loadTableValue(abs)
val w = getLong(abs + 1) ?: 255L val w = getLong() ?: 255L
pop() pop()
return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt())
@ -313,28 +313,28 @@ fun LuaThread.getAABB(stackIndex: Int = -1): AABB? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getDouble(abs + 1) val x = getDouble()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getDouble(abs + 1) val y = getDouble()
pop() pop()
y ?: return null y ?: return null
push(3) push(3)
loadTableValue(abs) loadTableValue(abs)
val z = getDouble(abs + 1) val z = getDouble()
pop() pop()
z ?: return null z ?: return null
push(4) push(4)
loadTableValue(abs) loadTableValue(abs)
val w = getDouble(abs + 1) val w = getDouble()
pop() pop()
w ?: return null w ?: return null
@ -343,10 +343,10 @@ fun LuaThread.getAABB(stackIndex: Int = -1): AABB? {
fun LuaThread.ArgStack.nextAABB(position: Int = this.position++): AABB { fun LuaThread.ArgStack.nextAABB(position: Int = this.position++): AABB {
if (position !in 1 ..this.top) 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) 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? { fun LuaThread.ArgStack.nextOptionalAABB(position: Int = this.position++): AABB? {
@ -365,28 +365,28 @@ fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? {
push(1) push(1)
loadTableValue(abs) loadTableValue(abs)
val x = getLong(abs + 1) val x = getLong()
pop() pop()
x ?: return null x ?: return null
push(2) push(2)
loadTableValue(abs) loadTableValue(abs)
val y = getLong(abs + 1) val y = getLong()
pop() pop()
y ?: return null y ?: return null
push(3) push(3)
loadTableValue(abs) loadTableValue(abs)
val z = getLong(abs + 1) val z = getLong()
pop() pop()
z ?: return null z ?: return null
push(4) push(4)
loadTableValue(abs) loadTableValue(abs)
val w = getLong(abs + 1) val w = getLong()
pop() pop()
w ?: return null w ?: return null

View File

@ -5,11 +5,12 @@ import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
import java.io.Closeable import java.io.Closeable
import java.lang.ref.Cleaner.Cleanable
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
import kotlin.properties.Delegates import kotlin.properties.Delegates
class LuaSharedState(val handlesThread: LuaThread) : Closeable { class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleanable) : Closeable {
private val pendingFree = ConcurrentLinkedQueue<Int>() private val pendingFree = ConcurrentLinkedQueue<Int>()
private val freeHandles = IntAVLTreeSet() private val freeHandles = IntAVLTreeSet()
private var nextHandle = 0 private var nextHandle = 0
@ -25,10 +26,15 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable {
var isValid = true var isValid = true
private set private set
fun ensureValid() {
check(isValid) { "Tried to use NULL LuaState!" }
}
override fun close() { override fun close() {
if (!isValid) return if (!isValid) return
isValid = false isValid = false
namedHandles.clear() namedHandles.clear()
cleanable.clean()
} }
fun initializeHandles(mainThread: LuaThread) { fun initializeHandles(mainThread: LuaThread) {
@ -51,7 +57,7 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable {
} }
fun cleanup() { fun cleanup() {
check(isValid) { "Shared state is no longer valid" } ensureValid()
if (handlesInUse == 0) return if (handlesInUse == 0) return
var handle = pendingFree.poll() var handle = pendingFree.poll()
@ -67,7 +73,7 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable {
} }
fun allocateHandle(name: Any?): LuaHandle { 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" } require(name == null || name !in namedHandles) { "Named handle '$name' already exists" }
handlesInUse++ handlesInUse++
@ -93,12 +99,12 @@ class LuaSharedState(val handlesThread: LuaThread) : Closeable {
} }
fun getNamedHandle(key: Any): LuaHandle { fun getNamedHandle(key: Any): LuaHandle {
check(isValid) { "Shared state is no longer valid" } ensureValid()
return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key") return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key")
} }
fun findNamedHandle(key: Any): LuaHandle? { fun findNamedHandle(key: Any): LuaHandle? {
check(isValid) { "Shared state is no longer valid" } ensureValid()
return namedHandles[key] return namedHandles[key]
} }
} }

View File

@ -56,18 +56,18 @@ class LuaThread private constructor(
CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false) CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false)
) )
this.cleanable = Cleaner.Cleanable {
LuaJNR.INSTANCE.lua_close(pointer)
panic.dispose()
}
panic.setAutoRelease(false) panic.setAutoRelease(false)
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address) LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
val handles = LuaJNR.INSTANCE.lua_newthread(pointer) val handles = LuaJNR.INSTANCE.lua_newthread(pointer)
storeRef(LUA_REGISTRYINDEX) LuaJNR.INSTANCE.luaL_ref(pointer, LUA_REGISTRYINDEX)
val handlesThread = LuaThread(handles, stringInterner) val handlesThread = LuaThread(handles, stringInterner)
sharedState = LuaSharedState(handlesThread)
sharedState = LuaSharedState(handlesThread, {
LuaJNR.INSTANCE.lua_close(pointer)
panic.dispose()
})
sharedState.handlesThread.sharedState = sharedState sharedState.handlesThread.sharedState = sharedState
push("__nils") push("__nils")
@ -140,13 +140,11 @@ class LuaThread private constructor(
} }
override fun close() { override fun close() {
if (cleanable != null) { sharedState.close()
cleanable!!.clean()
sharedState.close()
}
} }
val stackTop: Int get() { val stackTop: Int get() {
sharedState.ensureValid()
val value = LuaJNR.INSTANCE.lua_gettop(this.pointer) val value = LuaJNR.INSTANCE.lua_gettop(this.pointer)
check(value >= 0) { "Invalid stack top $value" } check(value >= 0) { "Invalid stack top $value" }
return 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). * 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 { fun absStackIndex(index: Int): Int {
sharedState.ensureValid()
if (index >= 0) if (index >= 0)
return index return index
@ -173,6 +172,7 @@ class LuaThread private constructor(
} }
fun load(code: String, chunkName: String = "@main chunk") { fun load(code: String, chunkName: String = "@main chunk") {
sharedState.ensureValid()
val bytes = code.toByteArray(charset = Charsets.UTF_8) val bytes = code.toByteArray(charset = Charsets.UTF_8)
val buf = ByteBuffer.allocateDirect(bytes.size) val buf = ByteBuffer.allocateDirect(bytes.size)
buf.order(ByteOrder.nativeOrder()) buf.order(ByteOrder.nativeOrder())
@ -319,6 +319,7 @@ class LuaThread private constructor(
private var initCalled = false private var initCalled = false
fun initScripts(callInit: Boolean = true): Boolean { fun initScripts(callInit: Boolean = true): Boolean {
sharedState.ensureValid()
check(!initCalled) { "Already initialized scripts!" } check(!initCalled) { "Already initialized scripts!" }
initCalled = true initCalled = true
@ -362,10 +363,12 @@ class LuaThread private constructor(
} }
fun attach(script: AssetPath) { fun attach(script: AssetPath) {
sharedState.ensureValid()
attach(script.fullPath) attach(script.fullPath)
} }
fun attach(script: String) { fun attach(script: String) {
sharedState.ensureValid()
if (initCalled) { if (initCalled) {
// minor hiccups during unpopulated script cache should be tolerable // minor hiccups during unpopulated script cache should be tolerable
load(Starbound.readLuaScript(script).join(), "@$script") 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? { 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" } require(limit <= Int.MAX_VALUE) { "Can't allocate string bigger than ${Int.MAX_VALUE} characters" }
val stack = MemoryStack.stackPush() val stack = MemoryStack.stackPush()
val status = stack.mallocLong(1) val status = stack.mallocLong(1)
@ -410,19 +414,19 @@ class LuaThread private constructor(
return readBytes.toString(charset = Charsets.UTF_8) return readBytes.toString(charset = Charsets.UTF_8)
} }
fun isCFunction(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_iscfunction(this.pointer, this.absStackIndex(stackIndex)) > 0 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 = this.typeAt(stackIndex) == LuaType.FUNCTION fun isFunction(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.FUNCTION }
fun isInteger(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isinteger(this.pointer, this.absStackIndex(stackIndex)) > 0 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 = this.typeAt(stackIndex) == LuaType.LIGHTUSERDATA fun isLightUserdata(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.LIGHTUSERDATA }
fun isNil(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.NIL fun isNil(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.NIL }
fun isNone(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.NONE fun isNone(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.NONE }
fun isNoneOrNil(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex).let { it == LuaType.NIL || it == 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 = LuaJNR.INSTANCE.lua_isnumber(this.pointer, this.absStackIndex(stackIndex)) > 0 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 = LuaJNR.INSTANCE.lua_isstring(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 = this.typeAt(stackIndex) == LuaType.TABLE fun isTable(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.TABLE }
fun isThread(stackIndex: Int = -1): Boolean = this.typeAt(stackIndex) == LuaType.THREAD fun isThread(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.THREAD }
fun isUserdata(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isuserdata(this.pointer, this.absStackIndex(stackIndex)) > 0 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 = this.typeAt(stackIndex) == LuaType.BOOLEAN fun isBoolean(stackIndex: Int = -1): Boolean { sharedState.ensureValid(); return this.typeAt(stackIndex) == LuaType.BOOLEAN }
fun getBoolean(stackIndex: Int = -1): Boolean? { fun getBoolean(stackIndex: Int = -1): Boolean? {
if (!this.isBoolean(stackIndex)) if (!this.isBoolean(stackIndex))
@ -514,6 +518,7 @@ class LuaThread private constructor(
} }
fun typeAt(stackIndex: Int = -1): LuaType { fun typeAt(stackIndex: Int = -1): LuaType {
sharedState.ensureValid()
return LuaType.valueOf(LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex)) return LuaType.valueOf(LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex))
} }
@ -597,7 +602,7 @@ class LuaThread private constructor(
push() push()
val top = this.stackTop 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 key = this.getJson(top, limit = limit)
val value = this.getJson(top + 1, limit = limit) val value = this.getJson(top + 1, limit = limit)
@ -690,6 +695,7 @@ class LuaThread private constructor(
} }
fun getObject(stackIndex: Int = -1): Any? { fun getObject(stackIndex: Int = -1): Any? {
sharedState.ensureValid()
return LuaJNI.lua_tojobject(pointer.address(), stackIndex) return LuaJNI.lua_tojobject(pointer.address(), stackIndex)
} }
@ -785,6 +791,7 @@ class LuaThread private constructor(
} }
fun loadTableValue(stackIndex: Int = -2): LuaType { fun loadTableValue(stackIndex: Int = -2): LuaType {
sharedState.ensureValid()
return LuaType.valueOf(LuaJNR.INSTANCE.lua_gettable(this.pointer, stackIndex)) return LuaType.valueOf(LuaJNR.INSTANCE.lua_gettable(this.pointer, stackIndex))
} }
@ -850,16 +857,19 @@ class LuaThread private constructor(
} }
fun pop(amount: Int = 1) { fun pop(amount: Int = 1) {
sharedState.ensureValid()
require(amount >= 0) { "Invalid amount of values to pop: $amount" } require(amount >= 0) { "Invalid amount of values to pop: $amount" }
if (amount == 0) return if (amount == 0) return
LuaJNR.INSTANCE.lua_settop(this.pointer, -amount - 1) LuaJNR.INSTANCE.lua_settop(this.pointer, -amount - 1)
} }
fun storeGlobal(name: String) { fun storeGlobal(name: String) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_setglobal(this.pointer, name) LuaJNR.INSTANCE.lua_setglobal(this.pointer, name)
} }
fun loadGlobal(name: String): LuaType { fun loadGlobal(name: String): LuaType {
sharedState.ensureValid()
return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name)) return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name))
} }
@ -1163,6 +1173,7 @@ class LuaThread private constructor(
} }
fun push(function: Fn, performanceCritical: Boolean) { fun push(function: Fn, performanceCritical: Boolean) {
sharedState.ensureValid()
LuaJNI.lua_pushcclosure(pointer.address()) { LuaJNI.lua_pushcclosure(pointer.address()) {
closure(it, function, performanceCritical) closure(it, function, performanceCritical)
} }
@ -1213,6 +1224,7 @@ class LuaThread private constructor(
} }
fun ensureExtraCapacity(maxSize: Int): Boolean { fun ensureExtraCapacity(maxSize: Int): Boolean {
sharedState.ensureValid()
return LuaJNR.INSTANCE.lua_checkstack(pointer, maxSize) return LuaJNR.INSTANCE.lua_checkstack(pointer, maxSize)
} }
@ -1227,18 +1239,22 @@ class LuaThread private constructor(
} }
fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) { fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount) LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount)
} }
fun push() { fun push() {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_pushnil(this.pointer) LuaJNR.INSTANCE.lua_pushnil(this.pointer)
} }
fun push(value: Long) { fun push(value: Long) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value) LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value)
} }
fun push(value: Long?) { fun push(value: Long?) {
sharedState.ensureValid()
if (value == null) { if (value == null) {
LuaJNR.INSTANCE.lua_pushnil(pointer) LuaJNR.INSTANCE.lua_pushnil(pointer)
} else { } else {
@ -1247,18 +1263,22 @@ class LuaThread private constructor(
} }
fun push(value: Double) { fun push(value: Double) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value) LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value)
} }
fun push(value: Float) { fun push(value: Float) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value.toDouble()) LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value.toDouble())
} }
fun push(value: Boolean) { fun push(value: Boolean) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0) LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0)
} }
fun push(value: Double?) { fun push(value: Double?) {
sharedState.ensureValid()
if (value == null) { if (value == null) {
LuaJNR.INSTANCE.lua_pushnil(pointer) LuaJNR.INSTANCE.lua_pushnil(pointer)
} else { } else {
@ -1267,6 +1287,7 @@ class LuaThread private constructor(
} }
fun push(value: Float?) { fun push(value: Float?) {
sharedState.ensureValid()
if (value == null) { if (value == null) {
LuaJNR.INSTANCE.lua_pushnil(pointer) LuaJNR.INSTANCE.lua_pushnil(pointer)
} else { } else {
@ -1275,6 +1296,7 @@ class LuaThread private constructor(
} }
fun push(value: Boolean?) { fun push(value: Boolean?) {
sharedState.ensureValid()
if (value == null) { if (value == null) {
LuaJNR.INSTANCE.lua_pushnil(pointer) LuaJNR.INSTANCE.lua_pushnil(pointer)
} else { } else {
@ -1283,6 +1305,7 @@ class LuaThread private constructor(
} }
fun push(value: String?) { fun push(value: String?) {
sharedState.ensureValid()
if (value == null) { if (value == null) {
push() push()
} else { } else {
@ -1291,10 +1314,12 @@ class LuaThread private constructor(
} }
fun push(value: LuaHandle) { fun push(value: LuaHandle) {
sharedState.ensureValid()
value.push(this) value.push(this)
} }
fun pushObject(value: Any) { fun pushObject(value: Any) {
sharedState.ensureValid()
LuaJNI.lua_pushjobject(pointer.address(), value) LuaJNI.lua_pushjobject(pointer.address(), value)
} }
@ -1334,6 +1359,7 @@ class LuaThread private constructor(
} }
fun copy(fromIndex: Int, toIndex: Int) { fun copy(fromIndex: Int, toIndex: Int) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex) LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
} }
@ -1352,18 +1378,22 @@ class LuaThread private constructor(
} }
fun setTop(topIndex: Int) { fun setTop(topIndex: Int) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_settop(pointer, topIndex) LuaJNR.INSTANCE.lua_settop(pointer, topIndex)
} }
fun storeRef(tableIndex: Int) { fun storeRef(tableIndex: Int) {
sharedState.ensureValid()
LuaJNR.INSTANCE.luaL_ref(pointer, tableIndex) LuaJNR.INSTANCE.luaL_ref(pointer, tableIndex)
} }
fun pushTable(arraySize: Int = 0, hashSize: Int = 0) { fun pushTable(arraySize: Int = 0, hashSize: Int = 0) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_createtable(pointer, arraySize, hashSize) LuaJNR.INSTANCE.lua_createtable(pointer, arraySize, hashSize)
} }
fun setTableValue(stackIndex: Int = -3) { fun setTableValue(stackIndex: Int = -3) {
sharedState.ensureValid()
LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex) LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex)
} }

View File

@ -225,6 +225,26 @@ sealed class LegacyWorldStorage() : WorldStorage() {
} }
override fun saveEntities(pos: ChunkPos, entities: Collection<AbstractEntity>) { override fun saveEntities(pos: ChunkPos, entities: Collection<AbstractEntity>) {
// since entities access their Lua state on save, we must collect everything before "unloading" them from memory
val uniques = HashMap<String, Vector2d>()
val entityData = ArrayList<FastByteArrayOutputStream>()
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 { scope.launch {
val chunkX = pos.x val chunkX = pos.x
val chunkY = pos.y val chunkY = pos.y
@ -236,23 +256,11 @@ sealed class LegacyWorldStorage() : WorldStorage() {
val streamEntities = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buffEntities))) val streamEntities = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buffEntities)))
val streamUniques = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buffUniques))) val streamUniques = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buffUniques)))
val uniques = HashMap<String, Vector2d>()
try { try {
streamEntities.writeVarInt(entities.size) streamEntities.writeVarInt(entityData.size)
for (entity in entities) { for (data in entityData) {
Starbound.legacyStoreJson { streamEntities.write(data.array, 0, data.length)
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
}
} }
streamEntities.close() streamEntities.close()

View File

@ -264,31 +264,37 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
""".trimIndent()) """.trimIndent())
override fun saveEntities(pos: ChunkPos, entities: Collection<AbstractEntity>) { override fun saveEntities(pos: ChunkPos, entities: Collection<AbstractEntity>) {
// since entities access their Lua state on save, we must collect everything before "unloading" them from memory
val storeData = JsonArray()
val uniques = ArrayList<Runnable>()
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 { executor.execute {
entitiesSavepoint.execute { entitiesSavepoint.execute {
clearUniqueEntities.setInt(1, pos.x) clearUniqueEntities.setInt(1, pos.x)
clearUniqueEntities.setInt(2, pos.y) clearUniqueEntities.setInt(2, pos.y)
clearUniqueEntities.execute() clearUniqueEntities.execute()
val storeData = JsonArray() uniques.forEach { it.run() }
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()
}
}
}
writeEntities.setInt(1, pos.x) writeEntities.setInt(1, pos.x)
writeEntities.setInt(2, pos.y) writeEntities.setInt(2, pos.y)

View File

@ -231,7 +231,11 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
// bumpState(State.CAVE_LIQUID) // bumpState(State.CAVE_LIQUID)
for (obj in world.storage.loadEntities(pos).await()) { for (obj in world.storage.loadEntities(pos).await()) {
obj.joinWorld(world) try {
obj.joinWorld(world)
} catch (err: Exception) {
LOGGER.error("Exception while spawning entity $obj in world", err)
}
} }
bumpState(state) bumpState(state)

View File

@ -215,14 +215,13 @@ class ServerWorld private constructor(
it.client.enqueueWarp(WarpAlias.Return) it.client.enqueueWarp(WarpAlias.Return)
} }
callUninitOnEntities()
if (!uncleanShutdown) { if (!uncleanShutdown) {
saveMetadata() saveMetadata()
storage.commit() storage.commit()
} }
storage.close() storage.close()
callUninitOnEntities()
} }
} }

View File

@ -253,15 +253,50 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
check(!world.entities.containsKey(entityID)) { "Duplicate entity ID: $entityID" } check(!world.entities.containsKey(entityID)) { "Duplicate entity ID: $entityID" }
innerWorld = world innerWorld = world
uniqueID.get()?.let { try {
check(it !in world.uniqueEntities) { "Duplicate unique entity ID: $it" } uniqueID.get()?.let {
world.uniqueEntities[it] = this 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.entities[entityID] = this
world.entityList.add(this) world.entityList.add(this)
spatialEntry = world.entityIndex.Entry(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 (visibleToRemotes) {
if (world is ClientWorld && !isRemote) { if (world is ClientWorld && !isRemote) {

View File

@ -8,6 +8,7 @@ import com.google.gson.JsonElement
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
@ -404,9 +405,13 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
return return
} }
} else { } else {
luaUpdate.update(delta) { try {
luaMovement.clearControlsIfNeeded() luaUpdate.update(delta) {
forceRegions.clear() luaMovement.clearControlsIfNeeded()
forceRegions.clear()
}
} catch (err: Exception) {
LOGGER.error("Exception while ticking $this", err)
} }
if (shouldDie) { 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? { override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? {
return luaMessages.handle(message, connection == connectionID, arguments) ?: statusController.handleMessage(message, connection == connectionID, arguments) return luaMessages.handle(message, connection == connectionID, arguments) ?: statusController.handleMessage(message, connection == connectionID, arguments)
} }
companion object {
private val LOGGER = LogManager.getLogger()
}
} }

View File

@ -151,6 +151,12 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
// provideStatusControllerBindings(this, lua) // provided through provideEntityBindings // provideStatusControllerBindings(this, lua) // provided through provideEntityBindings
provideEntityBindings(entity, lua) 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: Once we have brand new object-oriented Lua API, expose proper entity bindings here
// TODO: Expose world bindings // TODO: Expose world bindings
lua.initScripts() lua.initScripts()
@ -261,13 +267,6 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
} else { } else {
animator = EffectAnimator(KOptional(config.primaryAnimationConfig.fullPath)) animator = EffectAnimator(KOptional(config.primaryAnimationConfig.fullPath))
animatorID = effectAnimators.add(animator) 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)
}
} }
} }

View File

@ -411,7 +411,7 @@ function seqNode:run(delta, blackboard)
self.calls = self.calls + 1 self.calls = self.calls + 1
local size = self.size local size = self.size
local isSelector = self.isSelector local isSelector = self.isSelector
`
while self.index <= size do while self.index <= size do
local child = self.children[self.index] local child = self.children[self.index]
local status = runAndReset(child, delta, blackboard) local status = runAndReset(child, delta, blackboard)

View File

@ -200,13 +200,13 @@ do
error('require: script path must be absolute: ' .. path) error('require: script path must be absolute: ' .. path)
end end
if loadedScripts[path] then return unpack(loadedScripts[path]) end if loadedScripts[path] then return table.unpack(loadedScripts[path]) end
local fn = __require(path) local fn = __require(path)
if fn then if fn then
local result = {fn(...)} local result = {fn(...)}
loadedScripts[path] = result loadedScripts[path] = result
return unpack(result) return table.unpack(result)
else else
print('Failed to require Lua script ' .. path) print('Failed to require Lua script ' .. path)
loadedScripts[path] = {} loadedScripts[path] = {}
@ -235,7 +235,7 @@ do
end end
low = 1 low = 1
high = tA high = a
if high % 1.0 ~= 0.0 then if high % 1.0 ~= 0.0 then
error('bad argument #1 to math.random: integer expected, got double', 2) 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) error('bad argument #2 to math.random: number expected, got ' .. tB, 2)
end end
low = tA low = a
high = tB high = b
if low % 1.0 ~= 0.0 then if low % 1.0 ~= 0.0 then
error('bad argument #1 to math.random: integer expected, got double', 2) error('bad argument #1 to math.random: integer expected, got double', 2)
@ -385,20 +385,4 @@ function mergeJson(base, with)
end end
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)

View File

@ -175,7 +175,7 @@ for fnName, implName in pairs(regularQueryFunctions) do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
else else
-- point + radius -- point + radius
@ -190,7 +190,7 @@ for fnName, implName in pairs(regularQueryFunctions) do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -218,7 +218,7 @@ for fnName, implName in pairs(regularQueryFunctions) do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -244,7 +244,7 @@ for fnName, implName in pairs(regularQueryFunctions) do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -284,7 +284,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
else else
-- point + radius -- point + radius
@ -300,7 +300,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -333,7 +333,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -364,7 +364,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -423,7 +423,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
else else
-- point + radius -- point + radius
@ -439,7 +439,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -468,7 +468,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end
@ -495,7 +495,7 @@ do
options.callScript, options.callScript,
options.callScriptArgs or {}, options.callScriptArgs or {},
options.callScriptResult, options.callScriptResult,
order(options.order, fullName), order(options.order, fullName)
) )
end end
end end