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.MemoryUtil
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
import ru.dbotthepony.kstarbound.util.random.random
import java.io.Closeable
import java.lang.ref.Cleaner
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.random.RandomGenerator
import kotlin.math.floor
import kotlin.properties.Delegates
import kotlin.system.exitProcess
@Suppress("unused")
@ -50,6 +55,8 @@ class LuaThread private constructor(
panic.setAutoRelease(false)
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
randomHolder = Delegate.Box(random())
LuaJNR.INSTANCE.luaopen_base(this.pointer)
this.storeGlobal("_G")
LuaJNR.INSTANCE.luaopen_table(this.pointer)
@ -70,13 +77,60 @@ class LuaThread private constructor(
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")
call()
}
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) {
randomHolder = other.randomHolder
}
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)
)
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()
}
@ -159,6 +213,66 @@ class LuaThread private constructor(
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? {
if (!this.isString(stackIndex))
return null
@ -263,20 +377,7 @@ class LuaThread private constructor(
}
fun typeAt(stackIndex: Int = -1): LuaType {
return when (val value = 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")
}
return LuaType.valueOf(LuaJNR.INSTANCE.lua_type(this.pointer, stackIndex))
}
/**
@ -621,8 +722,8 @@ class LuaThread private constructor(
LuaJNR.INSTANCE.lua_setglobal(this.pointer, name)
}
fun loadGlobal(name: String) {
LuaJNR.INSTANCE.lua_getglobal(this.pointer, name)
fun loadGlobal(name: String): LuaType {
return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name))
}
inner class ArgStack(val top: Int) {

View File

@ -6,6 +6,15 @@ LUA_HINT_NONE = 0
LUA_HINT_ARRAY = 1
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
local function __newindex(self, key, value)
local nils = getmetatable(self).__nils
@ -107,10 +116,12 @@ function jresize(self, target)
if meta and meta.__nils then
local indices = {}
local i = 1
for k, v in pairs(meta.__nils) do
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
@ -120,10 +131,12 @@ function jresize(self, target)
end
local indices = {}
local i = 1
for k, v in pairs(self) do
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
@ -132,14 +145,116 @@ function jresize(self, target)
end
end
local __print = __print
do
local __print = __print
function print(...)
local values = {...}
function print(...)
local values = {}
for i, v in ipairs(values) do
values[i] = tostring(v)
-- this is required for properly printing nils
for i = 1, select('#', ...) do
values[i] = tostring(select(i, ...))
end
__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
__print(table.concat(values, '\t'))
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