Complete utility bindings, pushing Java objects to Lua
This commit is contained in:
parent
658dffc832
commit
9687c25bb0
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ freetype-2.11.1.lib
|
|||||||
/lua-5.3.6.dll
|
/lua-5.3.6.dll
|
||||||
/lua-5.3.6.exp
|
/lua-5.3.6.exp
|
||||||
/lua-5.3.6.lib
|
/lua-5.3.6.lib
|
||||||
|
/lua_glue.ilk
|
||||||
|
/lua_glue.pdb
|
||||||
|
16
ADDITIONS.md
16
ADDITIONS.md
@ -122,6 +122,11 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
|
|||||||
* Added `sb.logFatal`, similar to other log functions
|
* Added `sb.logFatal`, similar to other log functions
|
||||||
* `print(...)` now prints to both console (stdout) and logs
|
* `print(...)` now prints to both console (stdout) and logs
|
||||||
* `sb.log` functions now accept everything `string.format` accepts, and not only `%s` and `%%`
|
* `sb.log` functions now accept everything `string.format` accepts, and not only `%s` and `%%`
|
||||||
|
* Added `noise:seed(): long` (object returned by `sb.makePerlinSource`)
|
||||||
|
* Added `noise:parameters(): Json` (object returned by `sb.makePerlinSource`)
|
||||||
|
* Added `noise:init(seed: long)`, re-initializes noise generator with new seed, but same parameters (object returned by `sb.makePerlinSource`)
|
||||||
|
* Added `math.clamp(value, min, max)`
|
||||||
|
* Added `math.lerp(t, a, b)`
|
||||||
|
|
||||||
## Random
|
## Random
|
||||||
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point
|
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point
|
||||||
@ -311,3 +316,14 @@ unless world is pre-generated in its entirety).
|
|||||||
|
|
||||||
## Plant drop entities (vines or steps dropping on ground)
|
## Plant drop entities (vines or steps dropping on ground)
|
||||||
* Collision is now determined using hull instead of rectangle
|
* Collision is now determined using hull instead of rectangle
|
||||||
|
|
||||||
|
# Technical differences
|
||||||
|
|
||||||
|
* Lighting engine is based off original code, but is heavily modified, such as:
|
||||||
|
* Before spreading point lights, potential rectangle is determined, to reduce required calculations
|
||||||
|
* Lights are clasterized, and clusters are processed together, **on different threads** (multithreading)
|
||||||
|
* Point lights are being spread along **both diagonals**, not only along left-right bottom-top diagonal (can be adjusted using "light quality" setting)
|
||||||
|
* While overall performance is marginally better than original game, and scales up to any number of cores, efficiency of spreading algorithm is worse than original
|
||||||
|
* Chunk rendering is split into render regions, which size can be adjusted in settings
|
||||||
|
* Increasing render region size will decrease CPU load when rendering world and increase GPU utilization efficiency, while hurting CPU performance on chunk updates, and vice versa
|
||||||
|
* Render region size themselves align with world borders, so 3000x2000 world would have 30x25 sized render regions
|
||||||
|
11
CHANGES.md
11
CHANGES.md
@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
### Technical differences
|
|
||||||
|
|
||||||
* Lighting engine is based off original code, but is heavily modified, such as:
|
|
||||||
* Before spreading point lights, potential rectangle is determined, to reduce required calculations
|
|
||||||
* Lights are clasterized, and clusters are processed together, **on different threads** (multithreading)
|
|
||||||
* Point lights are being spread along **both diagonals**, not only along left-right bottom-top diagonal (can be adjusted using "light quality" setting)
|
|
||||||
* While overall performance is marginally better than original game, and scales up to any number of cores, efficiency of spreading algorithm is worse than original
|
|
||||||
* Chunk rendering is split into render regions, which size can be adjusted in settings
|
|
||||||
* Increasing render region size will decrease CPU load when rendering world and increase GPU utilization efficiency, while hurting CPU performance on chunk updates, and vice versa
|
|
||||||
* Render region size themselves align with world borders, so 3000x2000 world would have 30x25 sized render regions
|
|
@ -1 +1,2 @@
|
|||||||
clang lua_glue.c -o lua_glue.dll -Iinclude -Iinclude/win32 -v -Xlinker /DLL -Xlinker /LIBPATH lua-5.3.6.lib
|
clang -g lua_glue.c -o lua_glue.dll -Iinclude -Iinclude/win32 -v -Xlinker /DLL -Xlinker /LIBPATH lua-5.3.6.lib
|
||||||
|
PAUSE
|
55
lua_glue.c
55
lua_glue.c
@ -14,7 +14,7 @@ static int lua_jniFunc(lua_State *state) {
|
|||||||
|
|
||||||
if (result <= -1) {
|
if (result <= -1) {
|
||||||
const char* errMsg = lua_tostring(state, -1);
|
const char* errMsg = lua_tostring(state, -1);
|
||||||
|
|
||||||
if (errMsg == NULL)
|
if (errMsg == NULL)
|
||||||
return luaL_error(state, "Internal JVM Error");
|
return luaL_error(state, "Internal JVM Error");
|
||||||
|
|
||||||
@ -24,22 +24,22 @@ static int lua_jniFunc(lua_State *state) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mark_closure_free(lua_State *state) {
|
static int remove_gc_root(lua_State *state) {
|
||||||
JNIEnv *env = (JNIEnv *) lua_touserdata(state, lua_upvalueindex(1));
|
JNIEnv *env = (JNIEnv *) lua_touserdata(state, lua_upvalueindex(1));
|
||||||
jobject* lua_JCClosure = (jobject*) lua_touserdata(state, 1);
|
jobject* obj = (jobject*) lua_touserdata(state, 1);
|
||||||
|
|
||||||
if (lua_JCClosure == NULL) {
|
if (obj == NULL) {
|
||||||
printf("Lua Glue: mark_closure_free: lua_JCClosure is NULL!\n");
|
printf("Lua Glue: remove_gc_root: obj is NULL! This is VERY LIKELY is going to result into a memory leak!\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
(*env)->DeleteGlobalRef(env, *lua_JCClosure);
|
(*env)->DeleteGlobalRef(env, *obj);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JNIEXPORT void JNICALL Java_ru_dbotthepony_kstarbound_lua_LuaJNI_lua_1pushcclosure(JNIEnv *env, jclass interface, jlong luaState, jobject lua_JCClosure) {
|
static JNIEXPORT void JNICALL Java_ru_dbotthepony_kstarbound_lua_LuaJNI_lua_1pushcclosure(JNIEnv *env, jclass interface, jlong luaState, jobject lua_JCClosure) {
|
||||||
lua_State* LluaState = (lua_State*) luaState;
|
lua_State* LluaState = (lua_State*) luaState;
|
||||||
|
|
||||||
jclass clazz = (*env)->GetObjectClass(env, lua_JCClosure);
|
jclass clazz = (*env)->GetObjectClass(env, lua_JCClosure);
|
||||||
|
|
||||||
if (clazz == NULL)
|
if (clazz == NULL)
|
||||||
@ -52,7 +52,6 @@ static JNIEXPORT void JNICALL Java_ru_dbotthepony_kstarbound_lua_LuaJNI_lua_1pus
|
|||||||
|
|
||||||
lua_pushlightuserdata(LluaState, env);
|
lua_pushlightuserdata(LluaState, env);
|
||||||
lua_pushlightuserdata(LluaState, callback);
|
lua_pushlightuserdata(LluaState, callback);
|
||||||
|
|
||||||
void *umemory = lua_newuserdata(LluaState, sizeof(jobject));
|
void *umemory = lua_newuserdata(LluaState, sizeof(jobject));
|
||||||
jobject* rawBlock = (jobject*) umemory;
|
jobject* rawBlock = (jobject*) umemory;
|
||||||
*rawBlock = (*env)->NewGlobalRef(env, lua_JCClosure);
|
*rawBlock = (*env)->NewGlobalRef(env, lua_JCClosure);
|
||||||
@ -65,13 +64,47 @@ static JNIEXPORT void JNICALL Java_ru_dbotthepony_kstarbound_lua_LuaJNI_lua_1pus
|
|||||||
|
|
||||||
// function()
|
// function()
|
||||||
lua_pushlightuserdata(LluaState, env);
|
lua_pushlightuserdata(LluaState, env);
|
||||||
lua_pushcclosure(LluaState, mark_closure_free, 1);
|
lua_pushcclosure(LluaState, remove_gc_root, 1);
|
||||||
|
|
||||||
// table.__gc = fn
|
// table.__gc = fn
|
||||||
lua_setfield(LluaState, tableIndex, "__gc");
|
lua_setfield(LluaState, tableIndex, "__gc");
|
||||||
|
|
||||||
// setmetatable(userdata, table)
|
// setmetatable(userdata, table)
|
||||||
lua_setmetatable(LluaState, userdataIndex);
|
lua_setmetatable(LluaState, userdataIndex);
|
||||||
|
|
||||||
lua_pushcclosure(LluaState, lua_jniFunc, 3);
|
lua_pushcclosure(LluaState, lua_jniFunc, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pushes Java object to Lua state, creating new GC root
|
||||||
|
// Created reference is full userdata, hence it can have metatables attached to it
|
||||||
|
// IT IS EXPECTED that before pushing object, at top of stack there is a table which is to be attached as metatable to newly created object
|
||||||
|
// After this function returns, freshly created userdata will replace metatable
|
||||||
|
static JNIEXPORT void JNICALL Java_ru_dbotthepony_kstarbound_lua_LuaJNI_lua_1pushjobject(JNIEnv *env, jclass interface, jlong luaState, jobject obj) {
|
||||||
|
lua_State* LluaState = (lua_State*) luaState;
|
||||||
|
|
||||||
|
jobject *umemory = (jobject *) lua_newuserdata(LluaState, sizeof(jobject));
|
||||||
|
*umemory = (*env)->NewGlobalRef(env, obj);
|
||||||
|
|
||||||
|
lua_pushlightuserdata(LluaState, env);
|
||||||
|
lua_pushcclosure(LluaState, remove_gc_root, 1);
|
||||||
|
lua_setfield(LluaState, -3, "__gc");
|
||||||
|
|
||||||
|
lua_pushnil(LluaState);
|
||||||
|
lua_copy(LluaState, -3, -1);
|
||||||
|
|
||||||
|
lua_setmetatable(LluaState, -2);
|
||||||
|
lua_copy(LluaState, -1, -2);
|
||||||
|
lua_settop(LluaState, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns NULL if index is invalid or doesn't contain userdata
|
||||||
|
static JNIEXPORT jobject JNICALL Java_ru_dbotthepony_kstarbound_lua_LuaJNI_lua_1tojobject(JNIEnv *env, jclass interface, jlong luaState, jint stackIndex) {
|
||||||
|
lua_State* LluaState = (lua_State*) luaState;
|
||||||
|
|
||||||
|
jobject *data = lua_touserdata(LluaState, stackIndex);
|
||||||
|
|
||||||
|
if (data == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return *data;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua;
|
package ru.dbotthepony.kstarbound.lua;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,6 +11,11 @@ import java.io.File;
|
|||||||
public final class LuaJNI {
|
public final class LuaJNI {
|
||||||
public static native void lua_pushcclosure(long luaState, lua_CClosure callback);
|
public static native void lua_pushcclosure(long luaState, lua_CClosure callback);
|
||||||
|
|
||||||
|
public static native void lua_pushjobject(long luaState, @NotNull Object value);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static native Object lua_tojobject(long luaState, int stackIndex);
|
||||||
|
|
||||||
public interface lua_CClosure {
|
public interface lua_CClosure {
|
||||||
// 0 - успешное выполнение
|
// 0 - успешное выполнение
|
||||||
// != 0 - была ошибка
|
// != 0 - была ошибка
|
||||||
|
39
src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaHandle.kt
Normal file
39
src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaHandle.kt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.lua
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
class LuaHandle(private val parent: LuaHandleThread, val handle: Int) : Closeable {
|
||||||
|
private val cleanables = ArrayList<Runnable>()
|
||||||
|
|
||||||
|
var isValid = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
val parent = parent
|
||||||
|
val handle = handle
|
||||||
|
|
||||||
|
cleanables.add(Starbound.CLEANER.register(this) {
|
||||||
|
parent.freeHandle(handle)
|
||||||
|
}::clean)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun push(into: LuaThread) {
|
||||||
|
check(isValid) { "Tried to use NULL handle!" }
|
||||||
|
parent.thread.push()
|
||||||
|
parent.thread.copy(handle, -1)
|
||||||
|
parent.thread.moveStackValuesOnto(into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onClose(cleanable: Runnable) {
|
||||||
|
check(isValid) { "No longer valid" }
|
||||||
|
cleanables.add(cleanable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (!isValid) return
|
||||||
|
cleanables.forEach { it.run() }
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.lua
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
class LuaHandleThread(mainThread: LuaThread) {
|
||||||
|
private val pendingFree = ConcurrentLinkedQueue<Int>()
|
||||||
|
private val freeHandles = IntAVLTreeSet()
|
||||||
|
private var nextHandle = 0
|
||||||
|
// faster code path
|
||||||
|
private var handlesInUse = 0
|
||||||
|
|
||||||
|
val thread = mainThread.newThread(true)
|
||||||
|
|
||||||
|
init {
|
||||||
|
mainThread.storeRef(LuaThread.LUA_REGISTRYINDEX)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun freeHandle(handle: Int) {
|
||||||
|
pendingFree.add(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
if (handlesInUse == 0) return
|
||||||
|
var handle = pendingFree.poll()
|
||||||
|
|
||||||
|
while (handle != null) {
|
||||||
|
handlesInUse--
|
||||||
|
freeHandles.add(handle)
|
||||||
|
thread.push()
|
||||||
|
thread.copy(-1, handle)
|
||||||
|
thread.pop()
|
||||||
|
|
||||||
|
handle = pendingFree.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allocateHandle(): LuaHandle {
|
||||||
|
handlesInUse++
|
||||||
|
|
||||||
|
if (freeHandles.isEmpty()) {
|
||||||
|
return LuaHandle(this, ++nextHandle)
|
||||||
|
} else {
|
||||||
|
val handle = freeHandles.firstInt()
|
||||||
|
freeHandles.remove(handle)
|
||||||
|
|
||||||
|
thread.copy(-1, handle)
|
||||||
|
thread.pop()
|
||||||
|
return LuaHandle(this, handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ 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.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
@ -43,7 +44,8 @@ class LuaThread private constructor(
|
|||||||
val pointer = this.pointer
|
val pointer = this.pointer
|
||||||
val panic = ClosureManager.getInstance().newClosure(
|
val panic = ClosureManager.getInstance().newClosure(
|
||||||
{
|
{
|
||||||
LOGGER.fatal("Engine Error: LuaState at mem address $pointer has panicked!")
|
val errCode = getString()
|
||||||
|
LOGGER.fatal("Engine Error: LuaState at mem address $pointer has panicked!", RuntimeException(errCode))
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -59,6 +61,7 @@ class LuaThread private constructor(
|
|||||||
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
|
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
|
||||||
|
|
||||||
randomHolder = Delegate.Box(random())
|
randomHolder = Delegate.Box(random())
|
||||||
|
handleThread = LuaHandleThread(this)
|
||||||
|
|
||||||
LuaJNR.INSTANCE.luaopen_base(this.pointer)
|
LuaJNR.INSTANCE.luaopen_base(this.pointer)
|
||||||
this.storeGlobal("_G")
|
this.storeGlobal("_G")
|
||||||
@ -86,26 +89,34 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
private var cleanable: Cleaner.Cleanable? = null
|
private var cleanable: Cleaner.Cleanable? = null
|
||||||
private var randomHolder: Delegate<RandomGenerator> by Delegates.notNull()
|
private var randomHolder: Delegate<RandomGenerator> by Delegates.notNull()
|
||||||
|
private var handleThread by Delegates.notNull<LuaHandleThread>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for generating random numbers using math.random
|
* Responsible for generating random numbers using math.random
|
||||||
*
|
*
|
||||||
* Can be safely set to any other random number generator;
|
* Can be safely set to any other random number generator;
|
||||||
* math.randomseed sets this property to brand new generator with required seed
|
* math.randomseed sets this property to brand-new generator with required seed
|
||||||
*/
|
*/
|
||||||
var random: RandomGenerator
|
var random: RandomGenerator
|
||||||
get() = randomHolder.get()
|
get() = randomHolder.get()
|
||||||
set(value) = randomHolder.accept(value)
|
set(value) = randomHolder.accept(value)
|
||||||
|
|
||||||
private fun initializeFrom(other: LuaThread) {
|
private fun initializeFrom(other: LuaThread, skipHandle: Boolean) {
|
||||||
randomHolder = other.randomHolder
|
randomHolder = other.randomHolder
|
||||||
|
|
||||||
|
if (!skipHandle)
|
||||||
|
handleThread = other.handleThread
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newThread(): LuaThread {
|
private fun cleanup() {
|
||||||
|
handleThread.cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newThread(skipHandle: Boolean = false): LuaThread {
|
||||||
val pointer = LuaJNR.INSTANCE.lua_newthread(pointer)
|
val pointer = LuaJNR.INSTANCE.lua_newthread(pointer)
|
||||||
|
|
||||||
return LuaThread(pointer, stringInterner).also {
|
return LuaThread(pointer, stringInterner).also {
|
||||||
it.initializeFrom(this)
|
it.initializeFrom(this, skipHandle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +150,7 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(code: String, chunkName: String = "main chunk") {
|
fun load(code: String, chunkName: String = "@main chunk") {
|
||||||
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())
|
||||||
@ -172,6 +183,7 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun call(numArgs: Int = 0, numResults: Int = 0): Int {
|
fun call(numArgs: Int = 0, numResults: Int = 0): Int {
|
||||||
|
cleanup()
|
||||||
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
|
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
|
||||||
|
|
||||||
if (status == LUA_ERRRUN) {
|
if (status == LUA_ERRRUN) {
|
||||||
@ -607,6 +619,10 @@ class LuaThread private constructor(
|
|||||||
return pairs
|
return pairs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getObject(stackIndex: Int = -1): Any? {
|
||||||
|
return LuaJNI.lua_tojobject(pointer.address(), stackIndex)
|
||||||
|
}
|
||||||
|
|
||||||
fun iterateTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> Unit, valueVisitor: LuaThread.(stackIndex: Int) -> Unit) {
|
fun iterateTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> Unit, valueVisitor: LuaThread.(stackIndex: Int) -> Unit) {
|
||||||
val abs = this.absStackIndex(stackIndex)
|
val abs = this.absStackIndex(stackIndex)
|
||||||
|
|
||||||
@ -753,13 +769,18 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pop(amount: Int = 1): Int {
|
fun popObject(): Any? {
|
||||||
if (amount == 0) return 0
|
try {
|
||||||
check(amount > 0) { "Invalid amount to pop: $amount" }
|
return getObject()
|
||||||
val old = this.stackTop
|
} finally {
|
||||||
val new = (old - amount).coerceAtLeast(0)
|
pop()
|
||||||
LuaJNR.INSTANCE.lua_settop(this.pointer, new)
|
}
|
||||||
return old - new
|
}
|
||||||
|
|
||||||
|
fun pop(amount: Int = 1) {
|
||||||
|
require(amount >= 0) { "Invalid amount of values to pop: $amount" }
|
||||||
|
if (amount == 0) return
|
||||||
|
LuaJNR.INSTANCE.lua_settop(this.pointer, -amount - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeGlobal(name: String) {
|
fun storeGlobal(name: String) {
|
||||||
@ -785,6 +806,20 @@ class LuaThread private constructor(
|
|||||||
return position <= top
|
return position <= top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> nextObject(position: Int = this.position++): T {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: Java object expected, got nil")
|
||||||
|
|
||||||
|
val get = this@LuaThread.getObject(position)
|
||||||
|
|
||||||
|
if (get is T)
|
||||||
|
return get
|
||||||
|
else if (get != null)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: ${T::class.simpleName} expected, got ${get::class.simpleName}")
|
||||||
|
else
|
||||||
|
throw IllegalArgumentException("bad argument #$position: ${T::class.simpleName} expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
fun nextString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String {
|
fun nextString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String {
|
||||||
if (position !in 1 ..this.top)
|
if (position !in 1 ..this.top)
|
||||||
throw IllegalArgumentException("bad argument #$position: string expected, got nil")
|
throw IllegalArgumentException("bad argument #$position: string expected, got nil")
|
||||||
@ -888,11 +923,12 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
fun push(function: Fn, performanceCritical: Boolean) {
|
fun push(function: Fn, performanceCritical: Boolean) {
|
||||||
LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
|
LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
|
||||||
|
cleanup()
|
||||||
val realLuaState: LuaThread
|
val realLuaState: LuaThread
|
||||||
|
|
||||||
if (pointer.address() != it) {
|
if (pointer.address() != it) {
|
||||||
realLuaState = LuaThread(LuaJNR.RUNTIME.memoryManager.newPointer(it), stringInterner = stringInterner)
|
realLuaState = LuaThread(LuaJNR.RUNTIME.memoryManager.newPointer(it), stringInterner = stringInterner)
|
||||||
realLuaState.initializeFrom(this)
|
realLuaState.initializeFrom(this, false)
|
||||||
} else {
|
} else {
|
||||||
realLuaState = this
|
realLuaState = this
|
||||||
}
|
}
|
||||||
@ -955,6 +991,20 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES)
|
fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES)
|
||||||
|
|
||||||
|
fun interface Binding<T> {
|
||||||
|
fun invoke(self: T, arguments: ArgStack): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> pushBinding(fn: Binding<T>) {
|
||||||
|
push { fn.invoke(it.nextObject<T>(), it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> pushBinding(key: String, fn: Binding<T>) {
|
||||||
|
push(key)
|
||||||
|
pushBinding(fn)
|
||||||
|
setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) {
|
fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) {
|
||||||
LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount)
|
LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount)
|
||||||
}
|
}
|
||||||
@ -1015,6 +1065,54 @@ class LuaThread private constructor(
|
|||||||
pushStringIntoThread(this, value)
|
pushStringIntoThread(this, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun push(value: LuaHandle) {
|
||||||
|
value.push(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pushObject(value: Any) {
|
||||||
|
LuaJNI.lua_pushjobject(pointer.address(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a handle for top value in stack, allowing it to be referenced anywhere in engine's code
|
||||||
|
* without directly storing it anywhere in Lua's code
|
||||||
|
*/
|
||||||
|
fun createHandle(): LuaHandle {
|
||||||
|
push()
|
||||||
|
copy(-2, -1)
|
||||||
|
moveStackValuesOnto(handleThread.thread)
|
||||||
|
return handleThread.allocateHandle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val namedHandles = HashMap<Any, LuaHandle>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as [createHandle], but makes it permanent (unless manually closed through [LuaHandle.close])
|
||||||
|
*
|
||||||
|
* Makes handle available though [pushHandle] method
|
||||||
|
*
|
||||||
|
* This is useful for not creating cyclic references going through GC root
|
||||||
|
*/
|
||||||
|
fun createHandle(key: Any): LuaHandle {
|
||||||
|
require(key !in namedHandles) { "Named handle '$key' already exists" }
|
||||||
|
val handle = createHandle()
|
||||||
|
namedHandles[key] = handle
|
||||||
|
// onClose should be called only on same thread as Lua's, because it is invoked only on LuaHandle#close
|
||||||
|
handle.onClose { namedHandles.remove(key) }
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes handle to stack previously created by [createHandle] with [key] as its argument
|
||||||
|
*
|
||||||
|
* @throws NoSuchElementException if no such handle exists
|
||||||
|
*/
|
||||||
|
fun pushHandle(key: Any): LuaHandle {
|
||||||
|
val handle = namedHandles[key] ?: throw NoSuchElementException("No such handle: $key")
|
||||||
|
push(handle)
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
fun copy(fromIndex: Int, toIndex: Int) {
|
fun copy(fromIndex: Int, toIndex: Int) {
|
||||||
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
|
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
|
||||||
}
|
}
|
||||||
@ -1026,7 +1124,11 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
fun dup(fromIndex: Int) {
|
fun dup(fromIndex: Int) {
|
||||||
push()
|
push()
|
||||||
copy(fromIndex, -1)
|
|
||||||
|
if (fromIndex > 0)
|
||||||
|
copy(fromIndex, -1)
|
||||||
|
else
|
||||||
|
copy(fromIndex - 1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTop(topIndex: Int) {
|
fun setTop(topIndex: Int) {
|
||||||
@ -1308,5 +1410,8 @@ class LuaThread private constructor(
|
|||||||
const val LUA_HINT_NONE = 0
|
const val LUA_HINT_NONE = 0
|
||||||
const val LUA_HINT_ARRAY = 1
|
const val LUA_HINT_ARRAY = 1
|
||||||
const val LUA_HINT_OBJECT = 2
|
const val LUA_HINT_OBJECT = 2
|
||||||
|
|
||||||
|
const val LUAI_MAXSTACK = 1000000
|
||||||
|
const val LUA_REGISTRYINDEX = -LUAI_MAXSTACK - 1000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,67 +2,31 @@ package ru.dbotthepony.kstarbound.lua.bindings
|
|||||||
|
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
|
import com.google.gson.internal.bind.TypeAdapters
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
import org.classdump.luna.ByteString
|
import org.classdump.luna.ByteString
|
||||||
import org.classdump.luna.Table
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext
|
import org.classdump.luna.runtime.ExecutionContext
|
||||||
import ru.dbotthepony.kommons.util.XXHash32
|
import ru.dbotthepony.kommons.util.XXHash32
|
||||||
import ru.dbotthepony.kommons.util.XXHash64
|
import ru.dbotthepony.kommons.util.XXHash64
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||||
import ru.dbotthepony.kstarbound.json.JsonPath
|
import ru.dbotthepony.kstarbound.json.JsonPath
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaThread
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaThread.Companion
|
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaType
|
import ru.dbotthepony.kstarbound.lua.LuaType
|
||||||
import ru.dbotthepony.kstarbound.lua.from
|
import ru.dbotthepony.kstarbound.lua.from
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunctionArray
|
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
import ru.dbotthepony.kstarbound.lua.toJson
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandom
|
||||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
|
||||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
|
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandom32FromList
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64FromList
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandomDoubleFromList
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandomIntFromList
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandomLong
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandomLongFromList
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.toBytes
|
import ru.dbotthepony.kstarbound.util.random.toBytes
|
||||||
import ru.dbotthepony.kstarbound.util.toStarboundString
|
import ru.dbotthepony.kstarbound.util.toStarboundString
|
||||||
|
import java.io.StringWriter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
private val interpolateSinEase = luaFunctionArray { args ->
|
|
||||||
if (args.size < 3)
|
|
||||||
throw IllegalArgumentException("Invalid amount of arguments to interpolateSinEase: ${args.size}")
|
|
||||||
|
|
||||||
val offset = args[0] as? Number ?: throw IllegalArgumentException("Invalid 'offset' argument: ${args[0]}")
|
|
||||||
|
|
||||||
if (args[1] is Number && args[2] is Number) {
|
|
||||||
returnBuffer.setTo(Interpolator.Sin.interpolate(offset.toDouble(), (args[1] as Number).toDouble(), (args[2] as Number).toDouble()))
|
|
||||||
} else {
|
|
||||||
// assume vectors
|
|
||||||
val a = toVector2d(args[1]!!)
|
|
||||||
val b = toVector2d(args[2]!!)
|
|
||||||
|
|
||||||
val result = Vector2d(
|
|
||||||
Interpolator.Sin.interpolate(offset.toDouble(), a.x, b.x),
|
|
||||||
Interpolator.Sin.interpolate(offset.toDouble(), a.y, b.y),
|
|
||||||
)
|
|
||||||
|
|
||||||
returnBuffer.setTo(from(result))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Lua-side implementation for better performance?
|
// TODO: Lua-side implementation for better performance?
|
||||||
private fun replaceTags(args: LuaThread.ArgStack): Int {
|
private fun replaceTags(args: LuaThread.ArgStack): Int {
|
||||||
@ -80,10 +44,6 @@ private fun replaceTags(args: LuaThread.ArgStack): Int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private val makePerlinSource = luaFunction { settings: Table ->
|
|
||||||
returnBuffer.setTo(LuaPerlinNoise(AbstractPerlinNoise.of(Starbound.gson.fromJson(settings.toJson(), PerlinNoiseParameters::class.java))))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hash32(args: LuaThread.ArgStack): Int {
|
private fun hash32(args: LuaThread.ArgStack): Int {
|
||||||
val digest = XXHash32(2938728349.toInt())
|
val digest = XXHash32(2938728349.toInt())
|
||||||
|
|
||||||
@ -187,6 +147,24 @@ private fun jsonQuery(args: LuaThread.ArgStack): Int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun printJson(args: LuaThread.ArgStack): Int {
|
||||||
|
val json = args.nextJson()
|
||||||
|
val pretty = args.nextOptionalBoolean() ?: false
|
||||||
|
|
||||||
|
val strBuilder = StringWriter()
|
||||||
|
val writer = JsonWriter(strBuilder)
|
||||||
|
writer.isLenient = true
|
||||||
|
|
||||||
|
if (pretty) {
|
||||||
|
writer.setIndent(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeAdapters.JSON_ELEMENT.write(writer, json)
|
||||||
|
args.lua.push(strBuilder.toString())
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
fun provideUtilityBindings(lua: LuaThread) {
|
fun provideUtilityBindings(lua: LuaThread) {
|
||||||
with(lua) {
|
with(lua) {
|
||||||
push {
|
push {
|
||||||
@ -271,17 +249,64 @@ fun provideUtilityBindings(lua: LuaThread) {
|
|||||||
lua.setTableValue("staticRandomDouble", ::staticRandomDouble)
|
lua.setTableValue("staticRandomDouble", ::staticRandomDouble)
|
||||||
lua.setTableValue("staticRandomDoubleRange", ::staticRandomDoubleRange)
|
lua.setTableValue("staticRandomDoubleRange", ::staticRandomDoubleRange)
|
||||||
|
|
||||||
/*table["print"] = lua.globals["tostring"]
|
lua.pushTable()
|
||||||
table["printJson"] = lua.globals["tostring"]
|
val randomMeta = lua.createHandle()
|
||||||
table["interpolateSinEase"] = interpolateSinEase
|
|
||||||
table["makeRandomSource"] = luaFunction { seed: Long? ->
|
|
||||||
returnBuffer.setTo(LuaRandomGenerator(random(seed ?: lua.random.nextLong())))
|
|
||||||
}
|
|
||||||
|
|
||||||
table["makePerlinSource"] = makePerlinSource*/
|
lua.pushBinding("init", LuaRandom::init)
|
||||||
|
lua.pushBinding("addEntropy", LuaRandom::addEntropy)
|
||||||
|
lua.pushBinding("randu32", LuaRandom::randu32)
|
||||||
|
lua.pushBinding("randi32", LuaRandom::randi32)
|
||||||
|
lua.pushBinding("randu64", LuaRandom::randu64)
|
||||||
|
lua.pushBinding("randi64", LuaRandom::randi64)
|
||||||
|
lua.pushBinding("randf", LuaRandom::randf)
|
||||||
|
lua.pushBinding("randd", LuaRandom::randd)
|
||||||
|
lua.pushBinding("randInt", LuaRandom::randLong)
|
||||||
|
lua.pushBinding("randUInt", LuaRandom::randLong)
|
||||||
|
lua.pushBinding("randLong", LuaRandom::randLong)
|
||||||
|
lua.pushBinding("randULong", LuaRandom::randLong)
|
||||||
|
lua.pushBinding("randn", LuaRandom::randn)
|
||||||
|
|
||||||
lua.pop()
|
lua.pop()
|
||||||
|
|
||||||
|
lua.push("makeRandomSource")
|
||||||
|
lua.push { args ->
|
||||||
|
val seed = args.nextOptionalLong() ?: args.lua.random.nextLong()
|
||||||
|
lua.pushTable()
|
||||||
|
lua.push("__index")
|
||||||
|
lua.push(randomMeta) // cyclic reference through GC root
|
||||||
|
lua.setTableValue()
|
||||||
|
lua.pushObject(LuaRandom(random(seed)))
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
lua.setTableValue()
|
||||||
|
|
||||||
|
lua.pushTable()
|
||||||
|
val noiseMeta = lua.createHandle()
|
||||||
|
|
||||||
|
lua.pushBinding("get", LuaPerlinNoise::get)
|
||||||
|
lua.pushBinding("seed", LuaPerlinNoise::seed)
|
||||||
|
lua.pushBinding("parameters", LuaPerlinNoise::parameters)
|
||||||
|
lua.pushBinding("init", LuaPerlinNoise::init)
|
||||||
|
|
||||||
|
lua.pop()
|
||||||
|
|
||||||
|
lua.push("makePerlinSource")
|
||||||
|
lua.push { args ->
|
||||||
|
val params = AbstractPerlinNoise.of(Starbound.gson.fromJson(args.nextJson(), PerlinNoiseParameters::class.java))
|
||||||
|
lua.pushTable()
|
||||||
|
lua.push("__index")
|
||||||
|
lua.push(noiseMeta) // cyclic reference through GC root
|
||||||
|
lua.setTableValue()
|
||||||
|
lua.pushObject(LuaPerlinNoise(params))
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
lua.setTableValue()
|
||||||
|
|
||||||
|
lua.setTableValue("printJson", ::printJson)
|
||||||
|
|
||||||
|
lua.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) {
|
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) {
|
||||||
|
@ -1,50 +1,38 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.userdata
|
package ru.dbotthepony.kstarbound.lua.userdata
|
||||||
|
|
||||||
import org.classdump.luna.Table
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import org.classdump.luna.Userdata
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
import org.classdump.luna.impl.ImmutableTable
|
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder.Companion
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
|
|
||||||
class LuaPerlinNoise(val noise: AbstractPerlinNoise) : Userdata<AbstractPerlinNoise>() {
|
class LuaPerlinNoise(val noise: AbstractPerlinNoise) {
|
||||||
private var metatable: Table? = Companion.metatable
|
fun get(args: LuaThread.ArgStack): Int {
|
||||||
|
val x = args.nextDouble()
|
||||||
|
val y = args.nextOptionalDouble()
|
||||||
|
val z = args.nextOptionalDouble()
|
||||||
|
|
||||||
override fun getMetatable(): Table? {
|
if (y != null && z != null) {
|
||||||
return metatable
|
args.lua.push(noise[x, y, z])
|
||||||
}
|
} else if (y != null) {
|
||||||
|
args.lua.push(noise[x, y])
|
||||||
override fun setMetatable(mt: Table?): Table? {
|
} else {
|
||||||
val old = metatable
|
args.lua.push(noise[x])
|
||||||
metatable = mt
|
|
||||||
return old
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUserValue(): AbstractPerlinNoise {
|
|
||||||
return noise
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setUserValue(value: AbstractPerlinNoise?): AbstractPerlinNoise {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun __index(): Table {
|
|
||||||
return metatable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val metatable = ImmutableTable.Builder()
|
return 1
|
||||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
}
|
||||||
.add("get", luaFunction { self: LuaPerlinNoise, x: Number, y: Number?, z: Number? ->
|
|
||||||
if (y != null && z != null) {
|
fun seed(args: LuaThread.ArgStack): Int {
|
||||||
returnBuffer.setTo(self.noise[x.toDouble(), y.toDouble(), z.toDouble()])
|
args.lua.push(noise.seed)
|
||||||
} else if (y != null) {
|
return 1
|
||||||
returnBuffer.setTo(self.noise[x.toDouble(), y.toDouble()])
|
}
|
||||||
} else {
|
|
||||||
returnBuffer.setTo(self.noise[x.toDouble()])
|
fun parameters(args: LuaThread.ArgStack): Int {
|
||||||
}
|
args.lua.push(Starbound.gson.toJsonTree(noise.parameters))
|
||||||
})
|
return 1
|
||||||
.build()
|
}
|
||||||
|
|
||||||
|
fun init(args: LuaThread.ArgStack): Int {
|
||||||
|
noise.init(args.nextLong())
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.lua.userdata
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
|
class LuaRandom(var random: RandomGenerator) {
|
||||||
|
fun init(args: LuaThread.ArgStack): Int {
|
||||||
|
random = random(args.nextLong())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEntropy(args: LuaThread.ArgStack): Int {
|
||||||
|
throw UnsupportedOperationException("Adding entropy is not supported on new engine. If you have legitimate usecase for this, please let us know on issue tracker or discord")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randu32(args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(random.nextLong(0, Int.MAX_VALUE.toLong()))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randi32(args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(random.nextLong(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randu64(args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(random.nextLong(0, Long.MAX_VALUE))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randi64(args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(random.nextLong(Long.MIN_VALUE, Long.MAX_VALUE))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randf(args: LuaThread.ArgStack): Int {
|
||||||
|
val origin = args.nextOptionalDouble()?.toFloat()
|
||||||
|
val bound = args.nextOptionalDouble()?.toFloat()
|
||||||
|
|
||||||
|
if (origin == null || bound == null) {
|
||||||
|
args.lua.push(random.nextFloat())
|
||||||
|
} else {
|
||||||
|
require(origin <= bound) { "interval is empty: $origin <= $bound" }
|
||||||
|
|
||||||
|
if (origin == bound) {
|
||||||
|
// old behavior where it still updates internal random generator state
|
||||||
|
random.nextFloat()
|
||||||
|
args.lua.push(origin)
|
||||||
|
} else
|
||||||
|
args.lua.push(random.nextFloat(origin, bound))
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randd(args: LuaThread.ArgStack): Int {
|
||||||
|
val origin = args.nextOptionalDouble()
|
||||||
|
val bound = args.nextOptionalDouble()
|
||||||
|
|
||||||
|
if (origin == null || bound == null) {
|
||||||
|
args.lua.push(random.nextDouble())
|
||||||
|
} else {
|
||||||
|
require(origin <= bound) { "interval is empty: $origin <= $bound" }
|
||||||
|
|
||||||
|
if (origin == bound) {
|
||||||
|
// old behavior where it still updates internal random generator state
|
||||||
|
random.nextDouble()
|
||||||
|
args.lua.push(origin)
|
||||||
|
} else
|
||||||
|
args.lua.push(random.nextDouble(origin, bound))
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randLong(args: LuaThread.ArgStack): Int {
|
||||||
|
val origin = args.nextLong()
|
||||||
|
val bound = args.nextOptionalLong()
|
||||||
|
|
||||||
|
if (bound == null) {
|
||||||
|
if (origin == 0L) {
|
||||||
|
random.nextLong() // to keep old behavior
|
||||||
|
args.lua.push(0L)
|
||||||
|
} else if (origin < 0L) {
|
||||||
|
args.lua.push(random.nextLong(origin, 0L))
|
||||||
|
} else {
|
||||||
|
args.lua.push(random.nextLong(0L, origin))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bound == origin) {
|
||||||
|
// old behavior where it still updates internal random generator state
|
||||||
|
random.nextLong()
|
||||||
|
args.lua.push(origin)
|
||||||
|
} else {
|
||||||
|
args.lua.push(random.nextLong(origin, bound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randn(args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(random.nextNormalDouble(args.nextDouble(), args.nextDouble()))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
@ -1,114 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.userdata
|
|
||||||
|
|
||||||
import org.classdump.luna.Table
|
|
||||||
import org.classdump.luna.Userdata
|
|
||||||
import org.classdump.luna.impl.ImmutableTable
|
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
|
||||||
import java.util.random.RandomGenerator
|
|
||||||
|
|
||||||
class LuaRandomGenerator(var random: RandomGenerator) : Userdata<RandomGenerator>() {
|
|
||||||
private var metatable: Table? = Companion.metatable
|
|
||||||
|
|
||||||
override fun getMetatable(): Table? {
|
|
||||||
return metatable
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setMetatable(mt: Table?): Table? {
|
|
||||||
val old = metatable
|
|
||||||
metatable = mt
|
|
||||||
return old
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUserValue(): RandomGenerator {
|
|
||||||
return random
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setUserValue(value: RandomGenerator?): RandomGenerator {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun randf(origin: Double?, bound: Double?): Double {
|
|
||||||
if (origin != null && bound != null) {
|
|
||||||
if (origin == bound) {
|
|
||||||
random.nextDouble() // to keep old behavior
|
|
||||||
return origin
|
|
||||||
} else {
|
|
||||||
return random.nextDouble()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return random.nextDouble()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun randomInt(arg1: Long, arg2: Long?): Long {
|
|
||||||
if (arg2 == null) {
|
|
||||||
if (arg1 == 0L) {
|
|
||||||
random.nextLong() // to keep old behavior
|
|
||||||
return 0L
|
|
||||||
} else if (arg1 < 0L) {
|
|
||||||
return random.nextLong(arg1, 0L)
|
|
||||||
} else {
|
|
||||||
return random.nextLong(0L, arg1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (arg2 == arg1) {
|
|
||||||
random.nextLong() // to keep old behavior
|
|
||||||
return arg1
|
|
||||||
} else {
|
|
||||||
return random.nextLong(arg1, arg2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun __index(): Table {
|
|
||||||
return metatable
|
|
||||||
}
|
|
||||||
|
|
||||||
private val metatable = ImmutableTable.Builder()
|
|
||||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
|
||||||
.add("init", luaFunction { self: LuaRandomGenerator, seed: Long? ->
|
|
||||||
self.random = random(seed ?: System.nanoTime())
|
|
||||||
})
|
|
||||||
.add("addEntropy", luaFunction { self: LuaRandomGenerator ->
|
|
||||||
throw UnsupportedOperationException("Adding entropy is not supported on new engine. If you have legitimate usecase for this, please let us know on issue tracker or discord")
|
|
||||||
})
|
|
||||||
// TODO: Lua, by definition, has no unsigned numbers,
|
|
||||||
// and before 5.3, there were only doubles, longs (integers) were added
|
|
||||||
// in 5.3
|
|
||||||
.add("randu32", luaFunction { self: LuaRandomGenerator ->
|
|
||||||
returnBuffer.setTo(self.random.nextLong(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()))
|
|
||||||
})
|
|
||||||
.add("randi32", luaFunction { self: LuaRandomGenerator ->
|
|
||||||
returnBuffer.setTo(self.random.nextLong(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()))
|
|
||||||
})
|
|
||||||
.add("randu64", luaFunction { self: LuaRandomGenerator ->
|
|
||||||
returnBuffer.setTo(self.random.nextLong())
|
|
||||||
})
|
|
||||||
.add("randi64", luaFunction { self: LuaRandomGenerator ->
|
|
||||||
returnBuffer.setTo(self.random.nextLong())
|
|
||||||
})
|
|
||||||
.add("randf", luaFunction { self: LuaRandomGenerator, origin: Number?, bound: Number? ->
|
|
||||||
returnBuffer.setTo(self.randf(origin?.toDouble(), bound?.toDouble()))
|
|
||||||
})
|
|
||||||
.add("randd", luaFunction { self: LuaRandomGenerator, origin: Number?, bound: Number? ->
|
|
||||||
returnBuffer.setTo(self.randf(origin?.toDouble(), bound?.toDouble()))
|
|
||||||
})
|
|
||||||
.add("randb", luaFunction { self: LuaRandomGenerator ->
|
|
||||||
returnBuffer.setTo(self.random.nextBoolean())
|
|
||||||
})
|
|
||||||
.add("randInt", luaFunction { self: LuaRandomGenerator, arg1: Number, arg2: Number? ->
|
|
||||||
returnBuffer.setTo(self.randomInt(arg1.toLong(), arg2?.toLong()))
|
|
||||||
})
|
|
||||||
.add("randUInt", luaFunction { self: LuaRandomGenerator, arg1: Number, arg2: Number? ->
|
|
||||||
returnBuffer.setTo(self.randomInt(arg1.toLong(), arg2?.toLong()))
|
|
||||||
})
|
|
||||||
.add("randn", luaFunction { self: LuaRandomGenerator, arg1: Number, arg2: Number ->
|
|
||||||
returnBuffer.setTo(self.random.nextNormalDouble(arg1.toDouble(), arg2.toDouble()))
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,6 +14,13 @@ local setmetatable = setmetatable
|
|||||||
local select = select
|
local select = select
|
||||||
local math = math
|
local math = math
|
||||||
local string = string
|
local string = string
|
||||||
|
local format = string.format
|
||||||
|
|
||||||
|
local function checkarg(value, index, expected, fnName, overrideExpected)
|
||||||
|
if type(value) ~= expected then
|
||||||
|
error(string.format('bad argument #%d to %s: %s expected, got %s', index, fnName, overrideExpected or expected, type(value)), 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- 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)
|
||||||
@ -53,7 +60,7 @@ function jarray()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function jremove(self, key)
|
function jremove(self, key)
|
||||||
if type(self) ~= 'table' then error('bad argument #1 to jremove: table expected, got ' .. type(self), 2) end
|
checkarg(self, 1, 'table', 'jremove')
|
||||||
local meta = getmetatable(self)
|
local meta = getmetatable(self)
|
||||||
|
|
||||||
if meta and meta.__nils then
|
if meta and meta.__nils then
|
||||||
@ -64,7 +71,7 @@ function jremove(self, key)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function jsize(self)
|
function jsize(self)
|
||||||
if type(self) ~= 'table' then error('bad argument #1 to jsize: table expected, got ' .. type(self), 2) end
|
checkarg(self, 1, 'table', 'jsize')
|
||||||
|
|
||||||
local elemCount = 0
|
local elemCount = 0
|
||||||
local highestIndex = 0
|
local highestIndex = 0
|
||||||
@ -110,7 +117,8 @@ function jsize(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function jresize(self, target)
|
function jresize(self, target)
|
||||||
if type(self) ~= 'table' then error('bad argument #1 to jresize: table expected, got ' .. type(self), 2) end
|
checkarg(self, 1, 'table', 'jresize')
|
||||||
|
checkarg(target, 2, 'number', 'jresize')
|
||||||
|
|
||||||
local meta = getmetatable(self)
|
local meta = getmetatable(self)
|
||||||
|
|
||||||
@ -150,7 +158,6 @@ do
|
|||||||
local __print_warn = __print_warn
|
local __print_warn = __print_warn
|
||||||
local __print_error = __print_error
|
local __print_error = __print_error
|
||||||
local __print_fatal = __print_fatal
|
local __print_fatal = __print_fatal
|
||||||
local format = string.format
|
|
||||||
|
|
||||||
function print(...)
|
function print(...)
|
||||||
local values = {}
|
local values = {}
|
||||||
@ -185,9 +192,7 @@ do
|
|||||||
local loadedScripts = {}
|
local loadedScripts = {}
|
||||||
|
|
||||||
function require(path, ...)
|
function require(path, ...)
|
||||||
if type(path) ~= 'string' then
|
checkarg(path, 1, 'string', 'require')
|
||||||
error('bad argument #1 to require: string expected, got ' .. type(path), 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
if string.sub(path, 1, 1) ~= '/' then
|
if string.sub(path, 1, 1) ~= '/' then
|
||||||
error('require: script path must be absolute: ' .. path)
|
error('require: script path must be absolute: ' .. path)
|
||||||
@ -279,4 +284,53 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local min = math.min
|
||||||
|
local max = math.max
|
||||||
|
|
||||||
|
function math.clamp(value, _min, _max)
|
||||||
|
if _min > _max then
|
||||||
|
error(format('interval is empty: %d <= %d', _min, _max), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return max(min(value, _min), _max)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.lerp(t, a, b)
|
||||||
|
return t * (b - a) + a
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local lerp = math.lerp
|
||||||
|
local PI = math.pi
|
||||||
|
local sin = math.sin
|
||||||
|
|
||||||
|
local function lerpSin(t, a, b)
|
||||||
|
return lerp((sin(t * PI - PI / 2.0) + 1.0) / 2.0, a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sb.interpolateSinEase(t, a, b)
|
||||||
|
checkarg(t, 1, 'number', 'sb.interpolateSinEase')
|
||||||
|
|
||||||
|
local tA = type(a)
|
||||||
|
local tB = type(b)
|
||||||
|
|
||||||
|
if tA == 'number' and tB == 'number' then
|
||||||
|
return lerpSin(t, a, b)
|
||||||
|
elseif tA == 'table' and tB == 'table' then
|
||||||
|
checkarg(a[1], 2, 'number', 'sb.interpolateSinEase', 'number or vector')
|
||||||
|
checkarg(a[2], 2, 'number', 'sb.interpolateSinEase', 'number or vector')
|
||||||
|
|
||||||
|
checkarg(b[1], 3, 'number', 'sb.interpolateSinEase', 'number or vector')
|
||||||
|
checkarg(b[2], 3, 'number', 'sb.interpolateSinEase', 'number or vector')
|
||||||
|
|
||||||
|
return {lerpSin(t, a[1], b[1]), lerpSin(t, a[2], b[2])}
|
||||||
|
else
|
||||||
|
error('illegal arguments to sb.interpolateSinEase: ' .. tA .. ' ' .. tB)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- why is this even a thing.
|
||||||
|
sb.print = tostring
|
||||||
|
Loading…
Reference in New Issue
Block a user