PathController, PathFinder Actor movement controller Lua bindings Game loading no longer block Universe thread, more efficient registry population synchronization Environmental status effects now can be stat modifiers
334 lines
9.2 KiB
Kotlin
334 lines
9.2 KiB
Kotlin
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<ChunkFactory>()
|
|
private var initCalled = false
|
|
private val loadedScripts = ObjectArraySet<String>()
|
|
val require = LuaRequire()
|
|
|
|
inner class LuaRequire : AbstractFunction1<ByteString>() {
|
|
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) {
|
|
scripts.add(script)
|
|
}
|
|
|
|
fun attach(scripts: Collection<AssetPath>) {
|
|
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) {
|
|
attach(Starbound.loadScript(script.fullPath))
|
|
}
|
|
}
|
|
}
|
|
|
|
fun run(chunk: ChunkFactory): Array<out Any?> {
|
|
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<Any?> {
|
|
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<Any?> {
|
|
return executor.call(this, loader.compileTextChunk(chunk, name).newInstance(Variable(globals)))
|
|
}
|
|
|
|
companion object {
|
|
private val LOGGER = LogManager.getLogger()
|
|
private val COUNTER = AtomicLong()
|
|
}
|
|
}
|