оно крашится

This commit is contained in:
DBotThePony 2023-02-22 19:32:34 +07:00
parent 3f4c34f5e3
commit 645766ed42
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 477 additions and 219 deletions

View File

@ -25,7 +25,7 @@ application {
mainClass.set("ru.dbotthepony.kstarbound.MainKt")
}
java.toolchain.languageVersion.set(JavaLanguageVersion.of(17))
java.toolchain.languageVersion.set(JavaLanguageVersion.of(20))
tasks.compileKotlin {
kotlinOptions {
@ -77,7 +77,8 @@ dependencies {
runtimeOnly("org.lwjgl", "lwjgl-par", classifier = lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives)
implementation("net.java.dev.jna:jna:5.10.0")
implementation("net.java.dev.jna:jna:5.13.0")
implementation("com.github.jnr:jnr-ffi:2.2.13")
implementation("ru.dbotthepony:kbox2d:2.4.1.+")
implementation("ru.dbotthepony:kvector:1.3.2")

View File

@ -1,39 +1,84 @@
package ru.dbotthepony.kstarbound.lua;
import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.LongByReference;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.io.File;
public final class LuaJNA {
public static native int lua_pcallk(long p, int numArgs, int numResults, int msgh, Pointer ctx, LuaJNADynamic.lua_KFunction callback);
public static native int lua_callk(long p, int numArgs, int numResults, Pointer ctx, LuaJNADynamic.lua_KFunction callback);
@SuppressWarnings({"UnnecessaryModifier", "SpellCheckingInspection", "unused"})
public interface LuaJNA extends Library {
public int lua_pcallk(@NotNull Pointer luaState, int numArgs, int numResults, int msgh, Pointer ctx, lua_KFunction callback);
public int lua_callk(@NotNull Pointer luaState, int numArgs, int numResults, Pointer ctx, lua_KFunction callback);
public Pointer lua_atpanic(@NotNull Pointer luaState, @NotNull lua_CFunction fn);
public static native Pointer luaL_newstate();
public static native void lua_close(long pointer);
@Nullable
public Pointer luaL_newstate();
public void lua_close(@NotNull Pointer luaState);
// Стандартные библиотеки
public static native void luaopen_base(long pointer);
public static native void luaopen_package(long pointer);
public static native void luaopen_coroutine(long pointer);
public static native void luaopen_table(long pointer);
public static native void luaopen_io(long pointer);
public static native void luaopen_os(long pointer);
public static native void luaopen_string(long pointer);
public static native void luaopen_math(long pointer);
public static native void luaopen_utf8(long pointer);
public static native void luaopen_debug(long pointer);
public void luaopen_base(@NotNull Pointer luaState);
public void luaopen_package(@NotNull Pointer luaState);
public void luaopen_coroutine(@NotNull Pointer luaState);
public void luaopen_table(@NotNull Pointer luaState);
public void luaopen_io(@NotNull Pointer luaState);
public void luaopen_os(@NotNull Pointer luaState);
public void luaopen_string(@NotNull Pointer luaState);
public void luaopen_math(@NotNull Pointer luaState);
public void luaopen_utf8(@NotNull Pointer luaState);
public void luaopen_debug(@NotNull Pointer luaState);
public static native int lua_checkstack(long pointer, int value);
public static native int lua_absindex(long pointer, int value);
public static native int lua_gettop(long pointer);
public Pointer lua_tolstring(@NotNull Pointer luaState, int index, @NotNull LongByReference size);
public static native Pointer lua_tolstring(long pointer, int index, LongByReference size);
public int lua_load(@NotNull Pointer luaState, @NotNull lua_Reader reader, @Nullable Pointer userData, @NotNull String chunkName, @NotNull String mode);
public static native void lua_setglobal(long pointer, String name);
static {
Native.register(new File("./lua54.dll").getAbsolutePath());
public interface lua_CFunction extends Callback {
int invoke(@NotNull Pointer luaState);
}
public interface lua_Reader extends Callback {
@Nullable
Pointer readNextChunk(@NotNull Pointer luaState, @Nullable Pointer userData, @NotNull LongByReference sizeToRead);
}
public interface lua_KFunction extends Callback {
int invoke(@NotNull Pointer luaState, int status, Pointer context);
}
// Операции над стаком
// загрузка значений из Java на стек
public void lua_createtable(@NotNull Pointer luaState, int arraySize, int hashSize);
public void lua_pushnil(@NotNull Pointer luaState);
public void lua_pushnumber(@NotNull Pointer luaState, double value);
public void lua_pushinteger(@NotNull Pointer luaState, long value);
public void lua_pushboolean(@NotNull Pointer luaState, int value);
// NUL терминированная строка
public void lua_pushstring(@NotNull Pointer luaState, @NotNull String value);
// двоичная строка
@Nullable
public Pointer lua_pushlstring(@NotNull Pointer luaState, long stringPointer, long length);
// Загрузка Lua значений на стек
public int lua_getglobal(@NotNull Pointer luaState, @NotNull String name);
// запись значений со стека
public void lua_settable(@NotNull Pointer luaState, int stackIndex);
public void lua_setglobal(@NotNull Pointer luaState, @NotNull String name);
public int lua_checkstack(@NotNull Pointer luaState, int value);
public int lua_absindex(@NotNull Pointer luaState, int value);
/**
* Returns the index of the top element in the stack.
* Because indices start at 1, this result is equal to the number of elements in the stack; in particular, 0 means an empty stack.
*/
public int lua_gettop(@NotNull Pointer luaState);
public static final LuaJNA INSTANCE = Native.load("lua54", LuaJNA.class);
}

View File

@ -17,6 +17,6 @@ public final class LuaJNI {
}
static {
System.load(new File("lua_glue.dll").getAbsolutePath());
//System.load(new File("lua_glue.dll").getAbsolutePath());
}
}

View File

@ -0,0 +1,75 @@
package ru.dbotthepony.kstarbound.lua;
import jnr.ffi.LibraryLoader;
import jnr.ffi.Pointer;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings({"UnnecessaryModifier", "SpellCheckingInspection", "unused"})
public interface LuaJNR {
public int lua_pcallk(long luaState, int numArgs, int numResults, int msgh, long ctx, LuaJNA.lua_KFunction callback);
public int lua_callk(long luaState, int numArgs, int numResults, long ctx, LuaJNA.lua_KFunction callback);
public long lua_atpanic(long luaState, @NotNull LuaJNA.lua_CFunction fn);
public long luaL_newstate();
public void lua_close(long luaState);
// Стандартные библиотеки
public void luaopen_base(long luaState);
public void luaopen_package(long luaState);
public void luaopen_coroutine(long luaState);
public void luaopen_table(long luaState);
public void luaopen_io(long luaState);
public void luaopen_os(long luaState);
public void luaopen_string(long luaState);
public void luaopen_math(long luaState);
public void luaopen_utf8(long luaState);
public void luaopen_debug(long luaState);
public long lua_tolstring(long luaState, int index, @NotNull Pointer size);
public int lua_load(long luaState, @NotNull lua_Reader reader, long userData, @NotNull String chunkName, @NotNull String mode);
public interface lua_CFunction {
int invoke(long luaState);
}
public interface lua_Reader {
public long readNextChunk(long luaState, long userData, @NotNull Pointer sizeToRead);
}
public interface lua_KFunction {
public int invoke(long luaState, int status, long context);
}
// Операции над стаком
// загрузка значений из Java на стек
public void lua_createtable(long luaState, int arraySize, int hashSize);
public void lua_pushnil(long luaState);
public void lua_pushnumber(long luaState, double value);
public void lua_pushinteger(long luaState, long value);
public void lua_pushboolean(long luaState, int value);
// NUL терминированная строка
public void lua_pushstring(long luaState, @NotNull String value);
// двоичная строка
public long lua_pushlstring(long luaState, long stringPointer, long length);
// Загрузка Lua значений на стек
public int lua_getglobal(long luaState, @NotNull String name);
// запись значений со стека
public void lua_settable(long luaState, int stackIndex);
public void lua_setglobal(long luaState, @NotNull String name);
public int lua_checkstack(long luaState, int value);
public int lua_absindex(long luaState, int value);
/**
* Returns the index of the top element in the stack.
* Because indices start at 1, this result is equal to the number of elements in the stack; in particular, 0 means an empty stack.
*/
public int lua_gettop(long luaState);
public static final LuaJNR INSTANCE = LibraryLoader.create(LuaJNR.class).load("lua54");
}

View File

@ -1,7 +1,11 @@
package ru.dbotthepony.kstarbound
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.sun.jna.Native
import org.apache.logging.log4j.LogManager
import org.lwjgl.Version
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
@ -10,6 +14,7 @@ import ru.dbotthepony.kstarbound.client.render.Animator
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
import ru.dbotthepony.kstarbound.defs.item.DynamicItemDefinition
import ru.dbotthepony.kstarbound.io.BTreeDB
import ru.dbotthepony.kstarbound.lua.LuaState
import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
@ -17,11 +22,48 @@ import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import java.io.ByteArrayInputStream
import java.io.DataInputStream
import java.io.File
import java.util.Random
import java.util.zip.Inflater
private val LOGGER = LogManager.getLogger()
fun main() {
if (true) {
val lua = LuaState()
//Thread.sleep(5_000L)
lua.load(File("test.lua").readText())
//lua.load("print('hello world!', ...)")
//lua.push(GsonBuilder().create().fromJson(File("playerdata.json").reader(), JsonElement::class.java))
/*lua.push(JsonObject().also {
it.add("Сыр", JsonPrimitive("Гиршок"))
it.add("сас", JsonPrimitive("сос"))
it.add("сыс", JsonPrimitive(4))
it.add("ы", JsonNull.INSTANCE)
it.add("s", JsonObject().also {
it.add("Вложенный", JsonPrimitive("Объект!"))
})
})*/
lua.pcall()
for (t in 1 .. 100) {
lua.loadGlobal("test")
for (i in 0 until t)
lua.push("sass".repeat(t))
lua.pcall(t)
}
Thread.sleep(4_000L)
return
}
val starbound = Starbound()
LOGGER.info("Running LWJGL ${Version.getVersion()}")

View File

@ -1,193 +0,0 @@
package ru.dbotthepony.kstarbound.lua
import com.sun.jna.Callback
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Pointer
import com.sun.jna.ptr.LongByReference
import org.apache.logging.log4j.LogManager
import org.lwjgl.system.MemoryUtil
import java.io.Closeable
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.RuntimeException
import java.nio.ByteBuffer
import java.nio.ByteOrder
private typealias size_t = Long
private typealias psize_t = LongByReference
private typealias lua_KContext = Pointer
interface LuaJNADynamic : Library {
fun lua_atpanic(luaState: Pointer, fn: lua_CFunction): Pointer
fun lua_load(luaState: Pointer, reader: lua_Reader, data: Pointer?, chunkName: String, mode: String): Int
fun lua_pushlstring(luaState: Pointer, str: Pointer, len: size_t): Pointer?
interface lua_CFunction : Callback {
fun invoke(luaState: Pointer): Int
}
interface lua_Reader : Callback {
fun invoke(luaState: Pointer, data: Pointer?, size: psize_t): Pointer? /* String */
}
interface lua_KFunction : Callback {
/*
** Type for continuation functions
*/
fun invoke(luaState: Pointer, status: Int, ctx: lua_KContext?): Int
}
}
val LUA_JNA: LuaJNADynamic = Native.load("lua54", LuaJNADynamic::class.java)
private fun stringToBuffer(str: String): ByteBuffer {
val bytes = str.toByteArray(charset = Charsets.UTF_8)
val buf = ByteBuffer.allocateDirect(bytes.size)
buf.order(ByteOrder.nativeOrder())
bytes.forEach(buf::put)
buf.position(0)
return buf
}
private val SHARED_BUFFER = ByteBuffer.allocateDirect(16384).also { it.order(ByteOrder.nativeOrder()) }
private val SHARED_BUFFER_PTR = Pointer(MemoryUtil.memAddress(SHARED_BUFFER))
private fun loadString(string: String): Int {
SHARED_BUFFER.position(0)
val bytes = string.toByteArray(charset = Charsets.UTF_8)
bytes.forEach(SHARED_BUFFER::put)
SHARED_BUFFER.position(0)
return bytes.size
}
private fun lua_pushlstring(luaState: Pointer, str: String) {
val len = loadString(str)
LUA_JNA.lua_pushlstring(luaState, SHARED_BUFFER_PTR, len.toLong())
}
class LuaState : Closeable {
private val pointer = LuaJNA.luaL_newstate()
private val nativePointer = Pointer.nativeValue(pointer)
private var destroyed = false
private val panicHandler = object : LuaJNADynamic.lua_CFunction {
override fun invoke(luaState: Pointer): Int {
throw RuntimeException("$this panicked!")
}
}
init {
if (pointer == Pointer.NULL)
throw OutOfMemoryError("Unable to allocate new LuaState")
LUA_JNA.lua_atpanic(pointer, panicHandler)
LuaJNA.luaopen_base(nativePointer)
LuaJNA.luaopen_package(nativePointer)
LuaJNA.luaopen_table(nativePointer)
LuaJNA.luaopen_coroutine(nativePointer)
LuaJNA.luaopen_string(nativePointer)
LuaJNA.luaopen_math(nativePointer)
LuaJNA.luaopen_utf8(nativePointer)
LuaJNA.luaopen_debug(nativePointer)
pushClosure {
val build = mutableListOf<String?>()
for (i in 1 .. stackTop) {
build.add(getString(i))
}
LOGGER.info("Lua/print(): {}", build.joinToString("\t"))
}
LuaJNA.lua_setglobal(nativePointer, "print")
}
fun pushClosure(lambda: (state: LuaState) -> Unit) {
LuaJNI.lua_pushcclosure(nativePointer) lazy@{
try {
lambda.invoke(this@LuaState)
} catch (err: Throwable) {
val builder = StringWriter()
val printWriter = PrintWriter(builder)
err.printStackTrace(printWriter)
lua_pushlstring(pointer, builder.toString())
return@lazy 1
}
return@lazy 0
}
}
fun checkStack(minAmount: Int): Boolean {
return LuaJNA.lua_checkstack(nativePointer, minAmount) > 0
}
fun absoluteIndex(index: Int): Int {
return LuaJNA.lua_absindex(nativePointer, index)
}
fun load(code: String, chunkName: String = "main chunk") {
val buf = stringToBuffer(code)
throwLoadError(LUA_JNA.lua_load(pointer, object : LuaJNADynamic.lua_Reader {
override fun invoke(luaState: Pointer, data: Pointer?, size: psize_t): Pointer? {
if (buf.remaining() == 0) {
size.value = 0
return Pointer.NULL
}
size.value = buf.remaining().toLong()
val p = Pointer(MemoryUtil.memAddress(buf))
buf.position(buf.remaining())
return p
}
}, Pointer.NULL, chunkName, "t"))
}
fun pcall(numArgs: Int = 0, numResults: Int = 0): Int {
val status = LuaJNA.lua_pcallk(nativePointer, numArgs, numResults, 0, null, null)
if (status == LUA_ERRRUN) {
throw LuaRuntimeException(getString())
}
return status
}
fun getString(index: Int = -1, limit: Long = 4096): String? {
val len = psize_t()
val p = LuaJNA.lua_tolstring(nativePointer, absoluteIndex(index), len) ?: return null
if (len.value == 0L) {
return ""
}
if (len.value >= limit) {
throw IllegalStateException("Unreasonably long Lua string: ${len.value}")
}
val readBytes = ByteArray(len.value.toInt())
p.read(0L, readBytes, 0, readBytes.size)
return readBytes.toString(charset = Charsets.UTF_8)
}
val stackTop get() = LuaJNA.lua_gettop(nativePointer)
override fun close() {
if (destroyed) {
throw IllegalStateException("Already destroyed")
}
LuaJNA.lua_close(nativePointer)
destroyed = true
}
companion object {
private val LOGGER = LogManager.getLogger(LuaState::class.java)
}
}

View File

@ -0,0 +1,249 @@
package ru.dbotthepony.kstarbound.lua
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.sun.jna.Native
import com.sun.jna.Pointer
import com.sun.jna.ptr.LongByReference
import org.apache.logging.log4j.LogManager
import org.lwjgl.system.MemoryUtil
import java.io.Closeable
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.RuntimeException
import java.nio.ByteBuffer
import java.nio.ByteOrder
private fun stringToBuffer(str: String): ByteBuffer {
val bytes = str.toByteArray(charset = Charsets.UTF_8)
val buf = ByteBuffer.allocateDirect(bytes.size)
buf.order(ByteOrder.nativeOrder())
bytes.forEach(buf::put)
buf.position(0)
return buf
}
class LuaState : Closeable {
private val pointer = LuaJNA.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState")
private val nativePointer = Pointer.nativeValue(pointer)
private var destroyed = false
private val panicHandler = object : LuaJNA.lua_CFunction {
override fun invoke(luaState: Pointer): Int {
println("$this panicked!")
return 0
}
}
init {
LuaJNA.INSTANCE.lua_atpanic(pointer, panicHandler)
LuaJNA.INSTANCE.luaopen_base(pointer)
storeGlobal("_G")
LuaJNA.INSTANCE.luaopen_package(pointer)
storeGlobal("package")
LuaJNA.INSTANCE.luaopen_table(pointer)
storeGlobal("table")
LuaJNA.INSTANCE.luaopen_coroutine(pointer)
storeGlobal("coroutine")
LuaJNA.INSTANCE.luaopen_string(pointer)
storeGlobal("string")
LuaJNA.INSTANCE.luaopen_math(pointer)
storeGlobal("math")
LuaJNA.INSTANCE.luaopen_utf8(pointer)
storeGlobal("utf8")
LuaJNA.INSTANCE.luaopen_debug(pointer)
storeGlobal("debug")
/*
pushClosure {
val build = mutableListOf<String?>()
for (i in 1 .. stackTop) {
build.add(getString(i))
}
LOGGER.info("Lua/print(): {}", build.joinToString("\t"))
}
LuaJNA.lua_setglobal(pointer, "print")
*/
}
fun pushClosure(lambda: (state: LuaState) -> Unit) {
LuaJNI.lua_pushcclosure(nativePointer) lazy@{
try {
lambda.invoke(this@LuaState)
} catch (err: Throwable) {
val builder = StringWriter()
val printWriter = PrintWriter(builder)
err.printStackTrace(printWriter)
push(builder.toString())
return@lazy 1
}
return@lazy 0
}
}
fun checkStack(minAmount: Int): Boolean {
return LuaJNA.INSTANCE.lua_checkstack(pointer, minAmount) > 0
}
fun absoluteIndex(index: Int): Int {
return LuaJNA.INSTANCE.lua_absindex(pointer, index)
}
fun load(code: String, chunkName: String = "main chunk") {
val buf = stringToBuffer(code)
throwLoadError(LuaJNA.INSTANCE.lua_load(pointer, object : LuaJNA.lua_Reader {
override fun readNextChunk(luaState: Pointer, userdata: Pointer?, size: LongByReference): Pointer? {
if (buf.remaining() == 0) {
size.value = 0
return Pointer.NULL
}
size.value = buf.remaining().toLong()
val p = MemoryUtil.memAddress(buf)
buf.position(buf.remaining())
return Pointer(p)
}
}, Pointer.NULL, chunkName, "t"))
}
fun pcall(numArgs: Int = 0, numResults: Int = 0): Int {
val status = LuaJNA.INSTANCE.lua_pcallk(pointer, numArgs, numResults, 0, null, null)
if (status == LUA_ERRRUN) {
throw LuaRuntimeException(getString())
}
return status
}
fun getString(index: Int = -1, limit: Long = 4096): String? {
val len = LongByReference()
val p = LuaJNA.INSTANCE.lua_tolstring(pointer, absoluteIndex(index), len) ?: return null
if (len.value == 0L) {
return ""
}
if (len.value >= limit) {
throw IllegalStateException("Unreasonably long Lua string: ${len.value}")
}
val readBytes = ByteArray(len.value.toInt())
p.read(0L, readBytes, 0, readBytes.size)
return readBytes.toString(charset = Charsets.UTF_8)
}
override fun close() {
if (destroyed) {
throw IllegalStateException("Already destroyed")
}
LuaJNA.INSTANCE.lua_close(pointer)
destroyed = true
}
val stackTop: Int get() {
val value = LuaJNA.INSTANCE.lua_gettop(pointer)
check(value >= 0) { "Invalid stack top $value" }
return value
}
fun storeGlobal(name: String) {
LuaJNA.INSTANCE.lua_setglobal(pointer, name)
}
fun loadGlobal(name: String) {
LuaJNA.INSTANCE.lua_getglobal(pointer, name)
}
fun push() {
LuaJNA.INSTANCE.lua_pushnil(pointer)
}
fun push(value: Int) {
LuaJNA.INSTANCE.lua_pushinteger(pointer, value.toLong())
}
fun push(value: Long) {
LuaJNA.INSTANCE.lua_pushinteger(pointer, value)
}
fun push(value: String) {
val bytes = value.toByteArray(Charsets.UTF_8)
val block = Native.malloc(bytes.size.toLong())
if (block == 0L)
throw OutOfMemoryError("Unable to allocate ${bytes.size} bytes on heap")
try {
Pointer(block).write(0L, bytes, 0, bytes.size)
LuaJNA.INSTANCE.lua_pushlstring(pointer, block, bytes.size.toLong())
} finally {
Native.free(block)
}
}
fun push(value: JsonElement) {
when (value) {
JsonNull.INSTANCE -> {
LuaJNA.INSTANCE.lua_pushnil(pointer)
}
is JsonPrimitive -> {
if (value.isNumber) {
val num = value.asNumber
when (num) {
is Int, is Long -> LuaJNA.INSTANCE.lua_pushinteger(pointer, num.toLong())
else -> LuaJNA.INSTANCE.lua_pushnumber(pointer, num.toDouble())
}
} else if (value.isString) {
push(value.asString)
} else if (value.isBoolean) {
LuaJNA.INSTANCE.lua_pushboolean(pointer, if (value.asBoolean) 1 else 0)
} else {
throw IllegalArgumentException(value.toString())
}
}
is JsonArray -> {
LuaJNA.INSTANCE.lua_createtable(pointer, value.size(), 0)
val index = stackTop
for ((i, v) in value.withIndex()) {
LuaJNA.INSTANCE.lua_pushinteger(pointer, i.toLong() + 1L)
push(v)
LuaJNA.INSTANCE.lua_settable(pointer, index)
}
}
is JsonObject -> {
LuaJNA.INSTANCE.lua_createtable(pointer, 0, value.size())
val index = stackTop
for ((k, v) in value.entrySet()) {
push(k)
push(v)
LuaJNA.INSTANCE.lua_settable(pointer, index)
}
}
else -> {
throw IllegalArgumentException(value.toString())
}
}
}
}

View File

@ -0,0 +1,15 @@
package ru.dbotthepony.kstarbound.util
class RenderDirectives(val raw: String) {
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
override fun toString(): String {
return "RenderDirectives[$raw]"
}
}

24
test.lua Normal file
View File

@ -0,0 +1,24 @@
function printTable(input)
if type(input) ~= 'table' then
print(input)
return
end
for k, v in pairs(input) do
if type(v) == 'table' then
print(k .. ' = ')
printTable(v)
else
print(k .. ' = ', v, type(v))
end
end
end
function test(...)
print('Called test with ' .. select('#', ...) .. ' arguments: ' .. table.concat({...}, ', '))
end
print(collectgarbage('count') * 1024)
--printTable(select(1, ...))
--print('hello!')