require, math.random and math.randomseed implementations

This commit is contained in:
DBotThePony 2024-12-16 16:13:36 +07:00
parent c9be37e37b
commit f82b48672e
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 241 additions and 25 deletions

View File

@ -17,13 +17,18 @@ import org.apache.logging.log4j.LogManager
import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
import ru.dbotthepony.kstarbound.util.random.random
import java.io.Closeable import java.io.Closeable
import java.lang.ref.Cleaner import java.lang.ref.Cleaner
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.util.random.RandomGenerator
import kotlin.math.floor import kotlin.math.floor
import kotlin.properties.Delegates
import kotlin.system.exitProcess import kotlin.system.exitProcess
@Suppress("unused") @Suppress("unused")
@ -50,6 +55,8 @@ class LuaThread private constructor(
panic.setAutoRelease(false) panic.setAutoRelease(false)
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address) LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
randomHolder = Delegate.Box(random())
LuaJNR.INSTANCE.luaopen_base(this.pointer) LuaJNR.INSTANCE.luaopen_base(this.pointer)
this.storeGlobal("_G") this.storeGlobal("_G")
LuaJNR.INSTANCE.luaopen_table(this.pointer) LuaJNR.INSTANCE.luaopen_table(this.pointer)
@ -70,13 +77,60 @@ class LuaThread private constructor(
storeGlobal("__print") storeGlobal("__print")
push {
val path = getString()
try {
load(Starbound.readLuaScript(path).join(), "@$path")
1
} catch (err: Exception) {
LOGGER.error("Exception loading Lua script $path", err)
throw err
}
}
storeGlobal("__require")
push {
push(random.nextDouble())
1
}
storeGlobal("__random_double")
push {
push(random.nextLong(getLong(), getLong()))
1
}
storeGlobal("__random_long")
push {
random = random(getLong())
0
}
storeGlobal("__random_seed")
load(globalScript, "@starbound.jar!/scripts/global.lua") load(globalScript, "@starbound.jar!/scripts/global.lua")
call() call()
} }
private var cleanable: Cleaner.Cleanable? = null private var cleanable: Cleaner.Cleanable? = null
private var randomHolder: Delegate<RandomGenerator> by Delegates.notNull()
/**
* Responsible for generating random numbers using math.random
*
* Can be safely set to any other random number generator;
* math.randomseed sets this property to brand new generator with required seed
*/
var random: RandomGenerator
get() = randomHolder.get()
set(value) = randomHolder.accept(value)
private fun initializeFrom(other: LuaThread) { private fun initializeFrom(other: LuaThread) {
randomHolder = other.randomHolder
} }
fun newThread(): LuaThread { fun newThread(): LuaThread {
@ -145,7 +199,7 @@ class LuaThread private constructor(
CallContext.getCallContext(Type.POINTER, arrayOf(Type.POINTER, Type.ULONG_LONG, Type.POINTER), CallingConvention.DEFAULT, false) CallContext.getCallContext(Type.POINTER, arrayOf(Type.POINTER, Type.ULONG_LONG, Type.POINTER), CallingConvention.DEFAULT, false)
) )
this.throwLoadError(LuaJNR.INSTANCE.lua_load(this.pointer, closure.address, 0L, chunkName, "t")) throwLoadError(LuaJNR.INSTANCE.lua_load(pointer, closure.address, 0L, chunkName, "t"))
closure.dispose() closure.dispose()
} }
@ -159,6 +213,66 @@ class LuaThread private constructor(
return status return status
} }
private val attachedScripts = ArrayList<AssetPath>()
private var initCalled = false
fun initScripts(callInit: Boolean = true): Boolean {
check(!initCalled) { "Already initialized scripts!" }
initCalled = true
if (attachedScripts.isEmpty()) {
return true
}
val loadScripts = attachedScripts.map { Starbound.readLuaScript(it.fullPath) to it.fullPath }
attachedScripts.clear()
try {
// minor hiccups during unpopulated script cache should be tolerable
for ((chunk, path) in loadScripts) {
load(chunk.join(), "@$path")
call()
}
} catch (err: Exception) {
LOGGER.error("Failed to attach scripts to Lua environment", err)
return false
}
try {
if (callInit) {
val type = loadGlobal("init")
if (type == LuaType.FUNCTION) {
call()
} else if (type == LuaType.NIL || type == LuaType.NONE) {
pop()
} else {
pop()
throw LuaRuntimeException("init is not a function: $type")
}
}
} catch (err: Exception) {
LOGGER.error("Failed to call init() in Lua environment", err)
return false
}
return true
}
fun attach(script: AssetPath) {
if (initCalled) {
// minor hiccups during unpopulated script cache should be tolerable
load(Starbound.readLuaScript(script.fullPath).join(), "@" + script.fullPath)
call()
} else {
attachedScripts.add(script)
}
}
fun attach(script: Collection<AssetPath>) {
script.forEach { attach(it) }
}
fun getString(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? { fun getString(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? {
if (!this.isString(stackIndex)) if (!this.isString(stackIndex))
return null return null
@ -263,20 +377,7 @@ class LuaThread private constructor(
} }
fun typeAt(stackIndex: Int = -1): LuaType { fun typeAt(stackIndex: Int = -1): LuaType {
return when (val value = LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex)) { return LuaType.valueOf(LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex))
LUA_TNONE -> LuaType.NONE
LUA_TNIL -> LuaType.NIL
LUA_TBOOLEAN -> LuaType.BOOLEAN
LUA_TLIGHTUSERDATA -> LuaType.LIGHTUSERDATA
LUA_TNUMBER -> LuaType.NUMBER
LUA_TSTRING -> LuaType.STRING
LUA_TTABLE -> LuaType.TABLE
LUA_TFUNCTION -> LuaType.FUNCTION
LUA_TUSERDATA -> LuaType.USERDATA
LUA_TTHREAD -> LuaType.THREAD
LUA_NUMTYPES -> LuaType.UMTYPES
else -> throw RuntimeException("Invalid Lua type: $value")
}
} }
/** /**
@ -621,8 +722,8 @@ class LuaThread private constructor(
LuaJNR.INSTANCE.lua_setglobal(this.pointer, name) LuaJNR.INSTANCE.lua_setglobal(this.pointer, name)
} }
fun loadGlobal(name: String) { fun loadGlobal(name: String): LuaType {
LuaJNR.INSTANCE.lua_getglobal(this.pointer, name) return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name))
} }
inner class ArgStack(val top: Int) { inner class ArgStack(val top: Int) {

View File

@ -6,6 +6,15 @@ LUA_HINT_NONE = 0
LUA_HINT_ARRAY = 1 LUA_HINT_ARRAY = 1
LUA_HINT_OBJECT = 2 LUA_HINT_OBJECT = 2
local rawset = rawset
local type = type
local pairs = pairs
local ipairs = ipairs
local setmetatable = setmetatable
local select = select
local math = math
local string = string
-- this replicates original engine code, but it shouldn't work in first place -- this replicates original engine code, but it shouldn't work in first place
local function __newindex(self, key, value) local function __newindex(self, key, value)
local nils = getmetatable(self).__nils local nils = getmetatable(self).__nils
@ -107,10 +116,12 @@ function jresize(self, target)
if meta and meta.__nils then if meta and meta.__nils then
local indices = {} local indices = {}
local i = 1
for k, v in pairs(meta.__nils) do for k, v in pairs(meta.__nils) do
if type(k) == 'number' and k % 1.0 == 0.0 and k > target then if type(k) == 'number' and k % 1.0 == 0.0 and k > target then
table.insert(indices, k) indices[i] = k
i = i + 1
end end
end end
@ -120,10 +131,12 @@ function jresize(self, target)
end end
local indices = {} local indices = {}
local i = 1
for k, v in pairs(self) do for k, v in pairs(self) do
if type(k) == 'number' and k % 1.0 == 0.0 and k > target then if type(k) == 'number' and k % 1.0 == 0.0 and k > target then
table.insert(indices, k) indices[i] = k
i = i + 1
end end
end end
@ -132,14 +145,116 @@ function jresize(self, target)
end end
end end
local __print = __print do
local __print = __print
function print(...) function print(...)
local values = {...} local values = {}
for i, v in ipairs(values) do -- this is required for properly printing nils
values[i] = tostring(v) for i = 1, select('#', ...) do
values[i] = tostring(select(i, ...))
end end
__print(table.concat(values, '\t')) __print(table.concat(values, '\t'))
end
end
do
local __require = __require
local loadedScripts = {}
function require(path, ...)
if type(path) ~= 'string' then
error('bad argument #1 to require: string expected, got ' .. type(path), 2)
end
if string.sub(path, 1, 1) ~= '/' then
error('require: script path must be absolute: ' .. path)
end
if loadedScripts[path] then return unpack(loadedScripts[path]) end
local fn = __require(path)
if fn then
local result = {fn(...)}
loadedScripts[path] = result
return unpack(result)
else
print('Failed to require Lua script ' .. path)
loadedScripts[path] = {}
end
end
end
do
local __random_double = __random_double
local __random_long = __random_long
local __random_seed = __random_seed
local floor = math.floor
function math.random(a, b)
local tA = type(a)
local tB = type(b)
if tA == 'nil' and tB == 'nil' then
return __random_double()
else
local low, high
if tB == 'nil' then
if tA ~= 'number' then
error('bad argument #1 to math.random: integer expected, got ' .. tA, 2)
end
low = 1
high = tA
if high % 1.0 ~= 0.0 then
error('bad argument #1 to math.random: integer expected, got double', 2)
end
else
if tA ~= 'number' then
error('bad argument #1 to math.random: number expected, got ' .. tA, 2)
end
if tA ~= 'number' then
error('bad argument #2 to math.random: number expected, got ' .. tB, 2)
end
low = tA
high = tB
if low % 1.0 ~= 0.0 then
error('bad argument #1 to math.random: integer expected, got double', 2)
end
if high % 1.0 ~= 0.0 then
error('bad argument #2 to math.random: integer expected, got double', 2)
end
end
if low > high then
error('interval is empty (low: ' .. low .. ', high: ' .. high .. ')', 2)
elseif low == high then
return floor(low)
elseif high >= 9223372036854775807 || low <= -9223372036854775808 then
error('interval too large', 2)
else
return __random_long(floor(low), floor(high))
end
end
end
function math.randomseed(seed)
if type(seed) ~= 'number' then
error('bad argument #1 to math.randomseed: integer expected, got ' .. type(seed), 2)
end
if seed % 1.0 ~= 0.0 then
error('bad argument #1 to math.randomseed: integer expected, got double', 2)
end
__random_seed(floor(seed))
end
end end