340 lines
9.4 KiB
Kotlin
340 lines
9.4 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) {
|
|
if (initCalled) {
|
|
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
|
|
} else {
|
|
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) {
|
|
if (loadedScripts.add(script.fullPath)) {
|
|
this.scripts.add(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()
|
|
}
|
|
}
|