aboutsummaryrefslogtreecommitdiff
path: root/src/script/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/script/common')
-rw-r--r--src/script/common/CMakeLists.txt1
-rw-r--r--src/script/common/c_content.cpp91
-rw-r--r--src/script/common/c_content.h12
-rw-r--r--src/script/common/c_converter.cpp54
-rw-r--r--src/script/common/c_converter.h3
-rw-r--r--src/script/common/c_internal.cpp14
-rw-r--r--src/script/common/c_internal.h8
-rw-r--r--src/script/common/c_packer.cpp596
-rw-r--r--src/script/common/c_packer.h126
9 files changed, 807 insertions, 98 deletions
diff --git a/src/script/common/CMakeLists.txt b/src/script/common/CMakeLists.txt
index d07f6ab1b..3e84b46c7 100644
--- a/src/script/common/CMakeLists.txt
+++ b/src/script/common/CMakeLists.txt
@@ -3,6 +3,7 @@ set(common_SCRIPT_COMMON_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/c_converter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/c_types.cpp
${CMAKE_CURRENT_SOURCE_DIR}/c_internal.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/c_packer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/helper.cpp
PARENT_SCOPE)
diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp
index 8ca3a722f..0bdeaab9e 100644
--- a/src/script/common/c_content.cpp
+++ b/src/script/common/c_content.cpp
@@ -751,6 +751,9 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
// the slowest possible
f.liquid_viscosity = getintfield_default(L, index,
"liquid_viscosity", f.liquid_viscosity);
+ // If move_resistance is not set explicitly,
+ // move_resistance is equal to liquid_viscosity
+ f.move_resistance = f.liquid_viscosity;
f.liquid_range = getintfield_default(L, index,
"liquid_range", f.liquid_range);
f.leveled = getintfield_default(L, index, "leveled", f.leveled);
@@ -854,6 +857,21 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
getstringfield(L, index, "node_dig_prediction",
f.node_dig_prediction);
+ // How much the node slows down players, ranging from 1 to 7,
+ // the higher, the slower.
+ f.move_resistance = getintfield_default(L, index,
+ "move_resistance", f.move_resistance);
+
+ // Whether e.g. players in this node will have liquid movement physics
+ lua_getfield(L, index, "liquid_move_physics");
+ if(lua_isboolean(L, -1)) {
+ f.liquid_move_physics = lua_toboolean(L, -1);
+ } else if(lua_isnil(L, -1)) {
+ f.liquid_move_physics = f.liquid_type != LIQUID_NONE;
+ } else {
+ errorstream << "Field \"liquid_move_physics\": Invalid type!" << std::endl;
+ }
+ lua_pop(L, 1);
}
void push_content_features(lua_State *L, const ContentFeatures &c)
@@ -1004,6 +1022,10 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
lua_setfield(L, -2, "legacy_wallmounted");
lua_pushstring(L, c.node_dig_prediction.c_str());
lua_setfield(L, -2, "node_dig_prediction");
+ lua_pushnumber(L, c.move_resistance);
+ lua_setfield(L, -2, "move_resistance");
+ lua_pushboolean(L, c.liquid_move_physics);
+ lua_setfield(L, -2, "liquid_move_physics");
}
/******************************************************************************/
@@ -1384,24 +1406,27 @@ void push_tool_capabilities(lua_State *L,
}
/******************************************************************************/
-void push_inventory(lua_State *L, Inventory *inventory)
+void push_inventory_list(lua_State *L, const InventoryList &invlist)
{
- if (! inventory)
- throw SerializationError("Attempt to push nonexistant inventory");
- std::vector<const InventoryList*> lists = inventory->getLists();
- std::vector<const InventoryList*>::iterator iter = lists.begin();
+ push_items(L, invlist.getItems());
+}
+
+/******************************************************************************/
+void push_inventory_lists(lua_State *L, const Inventory &inv)
+{
+ const auto &lists = inv.getLists();
lua_createtable(L, 0, lists.size());
- for (; iter != lists.end(); iter++) {
- const char* name = (*iter)->getName().c_str();
- lua_pushstring(L, name);
- push_inventory_list(L, inventory, name);
+ for(const InventoryList *list : lists) {
+ const std::string &name = list->getName();
+ lua_pushlstring(L, name.c_str(), name.size());
+ push_inventory_list(L, *list);
lua_rawset(L, -3);
}
}
/******************************************************************************/
void read_inventory_list(lua_State *L, int tableindex,
- Inventory *inv, const char *name, Server* srv, int forcesize)
+ Inventory *inv, const char *name, IGameDef *gdef, int forcesize)
{
if(tableindex < 0)
tableindex = lua_gettop(L) + 1 + tableindex;
@@ -1413,7 +1438,7 @@ void read_inventory_list(lua_State *L, int tableindex,
}
// Get Lua-specified items to insert into the list
- std::vector<ItemStack> items = read_items(L, tableindex,srv);
+ std::vector<ItemStack> items = read_items(L, tableindex, gdef);
size_t listsize = (forcesize >= 0) ? forcesize : items.size();
// Create or resize/clear list
@@ -1430,19 +1455,6 @@ void read_inventory_list(lua_State *L, int tableindex,
}
}
-void push_inventory_list(lua_State *L, Inventory *inv, const char *name)
-{
- InventoryList *invlist = inv->getList(name);
- if(invlist == NULL){
- lua_pushnil(L);
- return;
- }
- std::vector<ItemStack> items;
- for(u32 i=0; i<invlist->getSize(); i++)
- items.push_back(invlist->getItem(i));
- push_items(L, items);
-}
-
/******************************************************************************/
struct TileAnimationParams read_animation_definition(lua_State *L, int index)
{
@@ -1701,7 +1713,7 @@ void push_items(lua_State *L, const std::vector<ItemStack> &items)
}
/******************************************************************************/
-std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv)
+std::vector<ItemStack> read_items(lua_State *L, int index, IGameDef *gdef)
{
if(index < 0)
index = lua_gettop(L) + 1 + index;
@@ -1717,7 +1729,7 @@ std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv)
if (items.size() < (u32) key) {
items.resize(key);
}
- items[key - 1] = read_item(L, -1, srv->idef());
+ items[key - 1] = read_item(L, -1, gdef->idef());
lua_pop(L, 1);
}
return items;
@@ -1768,24 +1780,19 @@ bool read_noiseparams(lua_State *L, int index, NoiseParams *np)
void push_noiseparams(lua_State *L, NoiseParams *np)
{
lua_newtable(L);
- push_float_string(L, np->offset);
- lua_setfield(L, -2, "offset");
- push_float_string(L, np->scale);
- lua_setfield(L, -2, "scale");
- push_float_string(L, np->persist);
- lua_setfield(L, -2, "persistence");
- push_float_string(L, np->lacunarity);
- lua_setfield(L, -2, "lacunarity");
- lua_pushnumber(L, np->seed);
- lua_setfield(L, -2, "seed");
- lua_pushnumber(L, np->octaves);
- lua_setfield(L, -2, "octaves");
+ setfloatfield(L, -1, "offset", np->offset);
+ setfloatfield(L, -1, "scale", np->scale);
+ setfloatfield(L, -1, "persist", np->persist);
+ setfloatfield(L, -1, "persistence", np->persist);
+ setfloatfield(L, -1, "lacunarity", np->lacunarity);
+ setintfield( L, -1, "seed", np->seed);
+ setintfield( L, -1, "octaves", np->octaves);
push_flags_string(L, flagdesc_noiseparams, np->flags,
np->flags);
lua_setfield(L, -2, "flags");
- push_v3_float_string(L, np->spread);
+ push_v3f(L, np->spread);
lua_setfield(L, -2, "spread");
}
@@ -2042,6 +2049,12 @@ void push_hud_element(lua_State *L, HudElement *elem)
lua_pushnumber(L, elem->number);
lua_setfield(L, -2, "number");
+ if (elem->type == HUD_ELEM_WAYPOINT) {
+ // waypoints reuse the item field to store precision, precision = item - 1
+ lua_pushnumber(L, elem->item - 1);
+ lua_setfield(L, -2, "precision");
+ }
+ // push the item field for waypoints as well for backwards compatibility
lua_pushnumber(L, elem->item);
lua_setfield(L, -2, "item");
diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h
index 1aed7901e..a6b96c012 100644
--- a/src/script/common/c_content.h
+++ b/src/script/common/c_content.h
@@ -55,10 +55,11 @@ struct ObjectProperties;
struct SimpleSoundSpec;
struct ServerSoundParams;
class Inventory;
+class InventoryList;
struct NodeBox;
struct ContentFeatures;
struct TileDef;
-class Server;
+class IGameDef;
struct DigParams;
struct HitParams;
struct EnumString;
@@ -124,11 +125,12 @@ void push_inventory (lua_State *L,
Inventory *inventory);
void push_inventory_list (lua_State *L,
- Inventory *inv,
- const char *name);
+ const InventoryList &invlist);
+void push_inventory_lists (lua_State *L,
+ const Inventory &inv);
void read_inventory_list (lua_State *L, int tableindex,
Inventory *inv, const char *name,
- Server *srv, int forcesize=-1);
+ IGameDef *gdef, int forcesize=-1);
MapNode readnode (lua_State *L, int index,
const NodeDefManager *ndef);
@@ -168,7 +170,7 @@ void push_items (lua_State *L,
std::vector<ItemStack> read_items (lua_State *L,
int index,
- Server* srv);
+ IGameDef* gdef);
void push_soundspec (lua_State *L,
const SimpleSoundSpec &spec);
diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp
index 19734b913..b5ff52f73 100644
--- a/src/script/common/c_converter.cpp
+++ b/src/script/common/c_converter.cpp
@@ -52,32 +52,12 @@ if (value < F1000_MIN || value > F1000_MAX) { \
/**
- * A helper which sets (if available) the vector metatable from builtin as metatable
- * for the table on top of the stack
+ * A helper which sets the vector metatable for the table on top of the stack
*/
static void set_vector_metatable(lua_State *L)
{
- // get vector.metatable
- lua_getglobal(L, "vector");
- if (!lua_istable(L, -1)) {
- // there is no global vector table
- lua_pop(L, 1);
- errorstream << "set_vector_metatable in c_converter.cpp: " <<
- "missing global vector table" << std::endl;
- return;
- }
- lua_getfield(L, -1, "metatable");
- // set the metatable
- lua_setmetatable(L, -3);
- // pop vector global
- lua_pop(L, 1);
-}
-
-
-void push_float_string(lua_State *L, float value)
-{
- auto str = ftos(value);
- lua_pushstring(L, str.c_str());
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE);
+ lua_setmetatable(L, -2);
}
void push_v3f(lua_State *L, v3f p)
@@ -101,26 +81,6 @@ void push_v2f(lua_State *L, v2f p)
lua_setfield(L, -2, "y");
}
-void push_v3_float_string(lua_State *L, v3f p)
-{
- lua_createtable(L, 0, 3);
- push_float_string(L, p.X);
- lua_setfield(L, -2, "x");
- push_float_string(L, p.Y);
- lua_setfield(L, -2, "y");
- push_float_string(L, p.Z);
- lua_setfield(L, -2, "z");
-}
-
-void push_v2_float_string(lua_State *L, v2f p)
-{
- lua_createtable(L, 0, 2);
- push_float_string(L, p.X);
- lua_setfield(L, -2, "x");
- push_float_string(L, p.Y);
- lua_setfield(L, -2, "y");
-}
-
v2s16 read_v2s16(lua_State *L, int index)
{
v2s16 p;
@@ -479,17 +439,9 @@ size_t read_stringlist(lua_State *L, int index, std::vector<std::string> *result
Table field getters
*/
-#if defined(__MINGW32__) && !defined(__MINGW64__)
-/* MinGW 32-bit somehow crashes in the std::set destructor when this
- * variable is thread-local, so just don't do that. */
-static std::set<u64> warned_msgs;
-#endif
-
bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname)
{
-#if !defined(__MINGW32__) || defined(__MINGW64__)
thread_local std::set<u64> warned_msgs;
-#endif
int t = lua_type(L, index);
if (t == LUA_TNIL)
diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h
index 6ad6f3212..a14eb9186 100644
--- a/src/script/common/c_converter.h
+++ b/src/script/common/c_converter.h
@@ -118,9 +118,6 @@ std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale);
size_t read_stringlist (lua_State *L, int index,
std::vector<std::string> *result);
-void push_float_string (lua_State *L, float value);
-void push_v3_float_string(lua_State *L, v3f p);
-void push_v2_float_string(lua_State *L, v2f p);
void push_v2s16 (lua_State *L, v2s16 p);
void push_v2s32 (lua_State *L, v2s32 p);
void push_v3s16 (lua_State *L, v3s16 p);
diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp
index df82dba14..ddd2d184c 100644
--- a/src/script/common/c_internal.cpp
+++ b/src/script/common/c_internal.cpp
@@ -166,3 +166,17 @@ void log_deprecated(lua_State *L, std::string message, int stack_depth)
infostream << script_get_backtrace(L) << std::endl;
}
+void call_string_dump(lua_State *L, int idx)
+{
+ // Retrieve string.dump from insecure env to avoid it being tampered with
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
+ if (!lua_isnil(L, -1))
+ lua_getfield(L, -1, "string");
+ else
+ lua_getglobal(L, "string");
+ lua_getfield(L, -1, "dump");
+ lua_remove(L, -2); // remove _G
+ lua_remove(L, -2); // remove 'string' table
+ lua_pushvalue(L, idx);
+ lua_call(L, 1, 1);
+}
diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h
index ab2d7b975..272a39941 100644
--- a/src/script/common/c_internal.h
+++ b/src/script/common/c_internal.h
@@ -54,6 +54,10 @@ extern "C" {
#define CUSTOM_RIDX_GLOBALS_BACKUP (CUSTOM_RIDX_BASE + 1)
#define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2)
#define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3)
+#define CUSTOM_RIDX_HTTP_API_LUA (CUSTOM_RIDX_BASE + 4)
+#define CUSTOM_RIDX_VECTOR_METATABLE (CUSTOM_RIDX_BASE + 5)
+#define CUSTOM_RIDX_METATABLE_MAP (CUSTOM_RIDX_BASE + 6)
+
// Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata
#if defined(__aarch64__) && USE_LUAJIT
@@ -136,3 +140,7 @@ DeprecatedHandlingMode get_deprecated_handling_mode();
* @param stack_depth How far on the stack to the first user function (ie: not builtin or core)
*/
void log_deprecated(lua_State *L, std::string message, int stack_depth = 1);
+
+// Safely call string.dump on a function value
+// (does not pop, leaves one value on stack)
+void call_string_dump(lua_State *L, int idx);
diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp
new file mode 100644
index 000000000..ede00c758
--- /dev/null
+++ b/src/script/common/c_packer.cpp
@@ -0,0 +1,596 @@
+/*
+Minetest
+Copyright (C) 2022 sfan5 <sfan5@live.de>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <cstdio>
+#include <cstring>
+#include <cmath>
+#include <cassert>
+#include <unordered_set>
+#include <unordered_map>
+#include "c_packer.h"
+#include "c_internal.h"
+#include "log.h"
+#include "debug.h"
+#include "threading/mutex_auto_lock.h"
+
+extern "C" {
+#include <lauxlib.h>
+}
+
+//
+// Helpers
+//
+
+// convert negative index to absolute position on Lua stack
+static inline int absidx(lua_State *L, int idx)
+{
+ assert(idx < 0);
+ return lua_gettop(L) + idx + 1;
+}
+
+// does the type put anything into PackedInstr::sdata?
+static inline bool uses_sdata(int type)
+{
+ switch (type) {
+ case LUA_TSTRING:
+ case LUA_TFUNCTION:
+ case LUA_TUSERDATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// does the type put anything into PackedInstr::<union>?
+static inline bool uses_union(int type)
+{
+ switch (type) {
+ case LUA_TNIL:
+ case LUA_TSTRING:
+ case LUA_TFUNCTION:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static inline bool can_set_into(int ktype, int vtype)
+{
+ switch (ktype) {
+ case LUA_TNUMBER:
+ return !uses_union(vtype);
+ case LUA_TSTRING:
+ return !uses_sdata(vtype);
+ default:
+ return false;
+ }
+}
+
+// is the key suitable for use with set_into?
+static inline bool suitable_key(lua_State *L, int idx)
+{
+ if (lua_type(L, idx) == LUA_TSTRING) {
+ // strings may not have a NULL byte (-> lua_setfield)
+ size_t len;
+ const char *str = lua_tolstring(L, idx, &len);
+ return strlen(str) == len;
+ } else {
+ assert(lua_type(L, idx) == LUA_TNUMBER);
+ // numbers must fit into an s32 and be integers (-> lua_rawseti)
+ lua_Number n = lua_tonumber(L, idx);
+ return std::floor(n) == n && n >= S32_MIN && n <= S32_MAX;
+ }
+}
+
+namespace {
+ // checks if you left any values on the stack, for debugging
+ class StackChecker {
+ lua_State *L;
+ int top;
+ public:
+ StackChecker(lua_State *L) : L(L), top(lua_gettop(L)) {}
+ ~StackChecker() {
+ assert(lua_gettop(L) >= top);
+ if (lua_gettop(L) > top) {
+ rawstream << "Lua stack not cleaned up: "
+ << lua_gettop(L) << " != " << top
+ << " (false-positive if exception thrown)" << std::endl;
+ }
+ }
+ };
+
+ // Since an std::vector may reallocate, this is the only safe way to keep
+ // a reference to a particular element.
+ template <typename T>
+ class VectorRef {
+ std::vector<T> *vec;
+ size_t idx;
+ VectorRef(std::vector<T> *vec, size_t idx) : vec(vec), idx(idx) {}
+ public:
+ constexpr VectorRef() : vec(nullptr), idx(0) {}
+ static VectorRef<T> front(std::vector<T> &vec) {
+ return VectorRef(&vec, 0);
+ }
+ static VectorRef<T> back(std::vector<T> &vec) {
+ return VectorRef(&vec, vec.size() - 1);
+ }
+ T &operator*() { return (*vec)[idx]; }
+ T *operator->() { return &(*vec)[idx]; }
+ operator bool() const { return vec != nullptr; }
+ };
+
+ struct Packer {
+ PackInFunc fin;
+ PackOutFunc fout;
+ };
+
+ typedef std::pair<std::string, Packer> PackerTuple;
+}
+
+static inline auto emplace(PackedValue &pv, s16 type)
+{
+ pv.i.emplace_back();
+ auto ref = VectorRef<PackedInstr>::back(pv.i);
+ ref->type = type;
+ // Initialize fields that may be left untouched
+ if (type == LUA_TTABLE) {
+ ref->uidata1 = 0;
+ ref->uidata2 = 0;
+ } else if (type == LUA_TUSERDATA) {
+ ref->ptrdata = nullptr;
+ } else if (type == INSTR_POP) {
+ ref->sidata2 = 0;
+ }
+ return ref;
+}
+
+//
+// Management of registered packers
+//
+
+static std::unordered_map<std::string, Packer> g_packers;
+static std::mutex g_packers_lock;
+
+void script_register_packer(lua_State *L, const char *regname,
+ PackInFunc fin, PackOutFunc fout)
+{
+ // Store away callbacks
+ {
+ MutexAutoLock autolock(g_packers_lock);
+ auto it = g_packers.find(regname);
+ if (it == g_packers.end()) {
+ auto &ref = g_packers[regname];
+ ref.fin = fin;
+ ref.fout = fout;
+ } else {
+ FATAL_ERROR_IF(it->second.fin != fin || it->second.fout != fout,
+ "Packer registered twice with mismatching callbacks");
+ }
+ }
+
+ // Save metatable so we can identify instances later
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP);
+ if (lua_isnil(L, -1)) {
+ lua_newtable(L);
+ lua_pushvalue(L, -1);
+ lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP);
+ }
+
+ luaL_getmetatable(L, regname);
+ FATAL_ERROR_IF(lua_isnil(L, -1), "No metatable registered with that name");
+
+ // CUSTOM_RIDX_METATABLE_MAP contains { [metatable] = "regname", ... }
+ // check first
+ lua_pushstring(L, regname);
+ lua_rawget(L, -3);
+ if (!lua_isnil(L, -1)) {
+ FATAL_ERROR_IF(lua_topointer(L, -1) != lua_topointer(L, -2),
+ "Packer registered twice with inconsistent metatable");
+ }
+ lua_pop(L, 1);
+ // then set
+ lua_pushstring(L, regname);
+ lua_rawset(L, -3);
+
+ lua_pop(L, 1);
+}
+
+static bool find_packer(const char *regname, PackerTuple &out)
+{
+ MutexAutoLock autolock(g_packers_lock);
+ auto it = g_packers.find(regname);
+ if (it == g_packers.end())
+ return false;
+ // copy data for thread safety
+ out.first = it->first;
+ out.second = it->second;
+ return true;
+}
+
+static bool find_packer(lua_State *L, int idx, PackerTuple &out)
+{
+#ifndef NDEBUG
+ StackChecker checker(L);
+#endif
+
+ // retrieve metatable of the object
+ if (lua_getmetatable(L, idx) != 1)
+ return false;
+
+ // use our global table to map it to the registry name
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP);
+ assert(lua_istable(L, -1));
+ lua_pushvalue(L, -2);
+ lua_rawget(L, -2);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 3);
+ return false;
+ }
+
+ // load the associated data
+ bool found = find_packer(lua_tostring(L, -1), out);
+ FATAL_ERROR_IF(!found, "Inconsistent internal state");
+ lua_pop(L, 3);
+ return true;
+}
+
+//
+// Packing implementation
+//
+
+static VectorRef<PackedInstr> record_object(lua_State *L, int idx, PackedValue &pv,
+ std::unordered_map<const void *, s32> &seen)
+{
+ const void *ptr = lua_topointer(L, idx);
+ assert(ptr);
+ auto found = seen.find(ptr);
+ if (found == seen.end()) {
+ seen[ptr] = pv.i.size();
+ return VectorRef<PackedInstr>();
+ }
+ s32 ref = found->second;
+ assert(ref < (s32)pv.i.size());
+ // reuse the value from first time
+ auto r = emplace(pv, INSTR_PUSHREF);
+ r->ref = ref;
+ pv.i[ref].keep_ref = true;
+ return r;
+}
+
+static VectorRef<PackedInstr> pack_inner(lua_State *L, int idx, int vidx, PackedValue &pv,
+ std::unordered_map<const void *, s32> &seen)
+{
+#ifndef NDEBUG
+ StackChecker checker(L);
+ assert(idx > 0);
+ assert(vidx > 0);
+#endif
+
+ switch (lua_type(L, idx)) {
+ case LUA_TNONE:
+ case LUA_TNIL:
+ return emplace(pv, LUA_TNIL);
+ case LUA_TBOOLEAN: {
+ auto r = emplace(pv, LUA_TBOOLEAN);
+ r->bdata = lua_toboolean(L, idx);
+ return r;
+ }
+ case LUA_TNUMBER: {
+ auto r = emplace(pv, LUA_TNUMBER);
+ r->ndata = lua_tonumber(L, idx);
+ return r;
+ }
+ case LUA_TSTRING: {
+ auto r = emplace(pv, LUA_TSTRING);
+ size_t len;
+ const char *str = lua_tolstring(L, idx, &len);
+ assert(str);
+ r->sdata.assign(str, len);
+ return r;
+ }
+ case LUA_TTABLE: {
+ auto r = record_object(L, idx, pv, seen);
+ if (r)
+ return r;
+ break; // execution continues
+ }
+ case LUA_TFUNCTION: {
+ auto r = record_object(L, idx, pv, seen);
+ if (r)
+ return r;
+ r = emplace(pv, LUA_TFUNCTION);
+ call_string_dump(L, idx);
+ size_t len;
+ const char *str = lua_tolstring(L, -1, &len);
+ assert(str);
+ r->sdata.assign(str, len);
+ lua_pop(L, 1);
+ return r;
+ }
+ case LUA_TUSERDATA: {
+ auto r = record_object(L, idx, pv, seen);
+ if (r)
+ return r;
+ PackerTuple ser;
+ if (!find_packer(L, idx, ser))
+ throw LuaError("Cannot serialize unsupported userdata");
+ pv.contains_userdata = true;
+ r = emplace(pv, LUA_TUSERDATA);
+ r->sdata = ser.first;
+ r->ptrdata = ser.second.fin(L, idx);
+ return r;
+ }
+ default: {
+ std::string err = "Cannot serialize type ";
+ err += lua_typename(L, lua_type(L, idx));
+ throw LuaError(err);
+ }
+ }
+
+ // LUA_TTABLE
+ lua_checkstack(L, 5);
+
+ auto rtable = emplace(pv, LUA_TTABLE);
+ const int vi_table = vidx++;
+
+ lua_pushnil(L);
+ while (lua_next(L, idx) != 0) {
+ // key at -2, value at -1
+ const int ktype = lua_type(L, -2), vtype = lua_type(L, -1);
+ if (ktype == LUA_TNUMBER)
+ rtable->uidata1++; // narr
+ else
+ rtable->uidata2++; // nrec
+
+ // check if we can use a shortcut
+ if (can_set_into(ktype, vtype) && suitable_key(L, -2)) {
+ // push only the value
+ auto rval = pack_inner(L, absidx(L, -1), vidx, pv, seen);
+ rval->pop = rval->type != LUA_TTABLE;
+ // and where to put it:
+ rval->set_into = vi_table;
+ if (ktype == LUA_TSTRING)
+ rval->sdata = lua_tostring(L, -2);
+ else
+ rval->sidata1 = lua_tointeger(L, -2);
+ // pop tables after the fact
+ if (!rval->pop) {
+ auto ri1 = emplace(pv, INSTR_POP);
+ ri1->sidata1 = vidx;
+ }
+ } else {
+ // push the key and value
+ pack_inner(L, absidx(L, -2), vidx, pv, seen);
+ vidx++;
+ pack_inner(L, absidx(L, -1), vidx, pv, seen);
+ vidx++;
+ // push an instruction to set them
+ auto ri1 = emplace(pv, INSTR_SETTABLE);
+ ri1->set_into = vi_table;
+ ri1->sidata1 = vidx - 2;
+ ri1->sidata2 = vidx - 1;
+ ri1->pop = true;
+ vidx -= 2;
+ }
+
+ lua_pop(L, 1);
+ }
+
+ assert(vidx == vi_table + 1);
+ return rtable;
+}
+
+PackedValue *script_pack(lua_State *L, int idx)
+{
+ if (idx < 0)
+ idx = absidx(L, idx);
+
+ PackedValue pv;
+ std::unordered_map<const void *, s32> seen;
+ pack_inner(L, idx, 1, pv, seen);
+
+ return new PackedValue(std::move(pv));
+}
+
+//
+// Unpacking implementation
+//
+
+void script_unpack(lua_State *L, PackedValue *pv)
+{
+ lua_newtable(L); // table at index top to track ref indices -> objects
+ const int top = lua_gettop(L);
+ int ctr = 0;
+
+ for (size_t packed_idx = 0; packed_idx < pv->i.size(); packed_idx++) {
+ auto &i = pv->i[packed_idx];
+
+ // If leaving values on stack make sure there's space (every 5th iteration)
+ if (!i.pop && (ctr++) >= 5) {
+ lua_checkstack(L, 5);
+ ctr = 0;
+ }
+
+ switch (i.type) {
+ /* Instructions */
+ case INSTR_SETTABLE:
+ lua_pushvalue(L, top + i.sidata1); // key
+ lua_pushvalue(L, top + i.sidata2); // value
+ lua_rawset(L, top + i.set_into);
+ if (i.pop) {
+ if (i.sidata1 != i.sidata2) {
+ // removing moves indices so pop higher index first
+ lua_remove(L, top + std::max(i.sidata1, i.sidata2));
+ lua_remove(L, top + std::min(i.sidata1, i.sidata2));
+ } else {
+ lua_remove(L, top + i.sidata1);
+ }
+ }
+ continue;
+ case INSTR_POP:
+ lua_remove(L, top + i.sidata1);
+ if (i.sidata2 > 0)
+ lua_remove(L, top + i.sidata2);
+ continue;
+ case INSTR_PUSHREF:
+ lua_pushinteger(L, i.ref);
+ lua_rawget(L, top);
+ break;
+
+ /* Lua types */
+ case LUA_TNIL:
+ lua_pushnil(L);
+ break;
+ case LUA_TBOOLEAN:
+ lua_pushboolean(L, i.bdata);
+ break;
+ case LUA_TNUMBER:
+ lua_pushnumber(L, i.ndata);
+ break;
+ case LUA_TSTRING:
+ lua_pushlstring(L, i.sdata.data(), i.sdata.size());
+ break;
+ case LUA_TTABLE:
+ lua_createtable(L, i.uidata1, i.uidata2);
+ break;
+ case LUA_TFUNCTION:
+ luaL_loadbuffer(L, i.sdata.data(), i.sdata.size(), nullptr);
+ break;
+ case LUA_TUSERDATA: {
+ PackerTuple ser;
+ sanity_check(find_packer(i.sdata.c_str(), ser));
+ ser.second.fout(L, i.ptrdata);
+ i.ptrdata = nullptr; // ownership taken by callback
+ break;
+ }
+
+ default:
+ assert(0);
+ break;
+ }
+
+ if (i.keep_ref) {
+ lua_pushinteger(L, packed_idx);
+ lua_pushvalue(L, -2);
+ lua_rawset(L, top);
+ }
+
+ if (i.set_into) {
+ if (!i.pop)
+ lua_pushvalue(L, -1);
+ if (uses_sdata(i.type))
+ lua_rawseti(L, top + i.set_into, i.sidata1);
+ else
+ lua_setfield(L, top + i.set_into, i.sdata.c_str());
+ } else {
+ if (i.pop)
+ lua_pop(L, 1);
+ }
+ }
+
+ // as part of the unpacking process we take ownership of all userdata
+ pv->contains_userdata = false;
+ // leave exactly one value on the stack
+ lua_settop(L, top+1);
+ lua_remove(L, top);
+}
+
+//
+// PackedValue
+//
+
+PackedValue::~PackedValue()
+{
+ if (!contains_userdata)
+ return;
+ for (auto &i : this->i) {
+ if (i.type == LUA_TUSERDATA && i.ptrdata) {
+ PackerTuple ser;
+ if (find_packer(i.sdata.c_str(), ser)) {
+ // tell it to deallocate object
+ ser.second.fout(nullptr, i.ptrdata);
+ } else {
+ assert(false);
+ }
+ }
+ }
+}
+
+//
+// script_dump_packed
+//
+
+#ifndef NDEBUG
+void script_dump_packed(const PackedValue *val)
+{
+ printf("instruction stream: [\n");
+ for (const auto &i : val->i) {
+ printf("\t(");
+ switch (i.type) {
+ case INSTR_SETTABLE:
+ printf("SETTABLE(%d, %d)", i.sidata1, i.sidata2);
+ break;
+ case INSTR_POP:
+ printf(i.sidata2 ? "POP(%d, %d)" : "POP(%d)", i.sidata1, i.sidata2);
+ break;
+ case INSTR_PUSHREF:
+ printf("PUSHREF(%d)", i.ref);
+ break;
+ case LUA_TNIL:
+ printf("nil");
+ break;
+ case LUA_TBOOLEAN:
+ printf(i.bdata ? "true" : "false");
+ break;
+ case LUA_TNUMBER:
+ printf("%f", i.ndata);
+ break;
+ case LUA_TSTRING:
+ printf("\"%s\"", i.sdata.c_str());
+ break;
+ case LUA_TTABLE:
+ printf("table(%d, %d)", i.uidata1, i.uidata2);
+ break;
+ case LUA_TFUNCTION:
+ printf("function(%d byte)", i.sdata.size());
+ break;
+ case LUA_TUSERDATA:
+ printf("userdata %s %p", i.sdata.c_str(), i.ptrdata);
+ break;
+ default:
+ printf("!!UNKNOWN!!");
+ break;
+ }
+ if (i.set_into) {
+ if (i.type >= 0 && uses_sdata(i.type))
+ printf(", k=%d, into=%d", i.sidata1, i.set_into);
+ else if (i.type >= 0)
+ printf(", k=\"%s\", into=%d", i.sdata.c_str(), i.set_into);
+ else
+ printf(", into=%d", i.set_into);
+ }
+ if (i.keep_ref)
+ printf(", keep_ref");
+ if (i.pop)
+ printf(", pop");
+ printf(")\n");
+ }
+ printf("]\n");
+}
+#endif
diff --git a/src/script/common/c_packer.h b/src/script/common/c_packer.h
new file mode 100644
index 000000000..fe072c10a
--- /dev/null
+++ b/src/script/common/c_packer.h
@@ -0,0 +1,126 @@
+/*
+Minetest
+Copyright (C) 2022 sfan5 <sfan5@live.de>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "irrlichttypes.h"
+#include "util/basic_macros.h"
+
+extern "C" {
+#include <lua.h>
+}
+
+/*
+ This file defines an in-memory representation of Lua objects including
+ support for functions and userdata. It it used to move data between Lua
+ states and cannot be used for persistence or network transfer.
+*/
+
+#define INSTR_SETTABLE (-10)
+#define INSTR_POP (-11)
+#define INSTR_PUSHREF (-12)
+
+/**
+ * Represents a single instruction that pushes a new value or works with existing ones.
+ */
+struct PackedInstr
+{
+ s16 type; // LUA_T* or INSTR_*
+ u16 set_into; // set into table on stack
+ bool keep_ref; // is referenced later by INSTR_PUSHREF?
+ bool pop; // remove from stack?
+ union {
+ bool bdata; // boolean: value
+ lua_Number ndata; // number: value
+ struct {
+ u16 uidata1, uidata2; // table: narr, nrec
+ };
+ struct {
+ /*
+ SETTABLE: key index, value index
+ POP: indices to remove
+ otherwise w/ set_into: numeric key, -
+ */
+ s32 sidata1, sidata2;
+ };
+ void *ptrdata; // userdata: implementation defined
+ s32 ref; // PUSHREF: index of referenced instr
+ };
+ /*
+ - string: value
+ - function: buffer
+ - w/ set_into: string key (no null bytes!)
+ - userdata: name in registry
+ */
+ std::string sdata;
+
+ PackedInstr() : type(0), set_into(0), keep_ref(false), pop(false) {}
+};
+
+/**
+ * A packed value can be a primitive like a string or number but also a table
+ * including all of its contents. It is made up of a linear stream of
+ * 'instructions' that build the final value when executed.
+ */
+struct PackedValue
+{
+ std::vector<PackedInstr> i;
+ // Indicates whether there are any userdata pointers that need to be deallocated
+ bool contains_userdata = false;
+
+ PackedValue() = default;
+ ~PackedValue();
+
+ DISABLE_CLASS_COPY(PackedValue)
+
+ ALLOW_CLASS_MOVE(PackedValue)
+};
+
+/*
+ * Packing callback: Turns a Lua value at given index into a void*
+ */
+typedef void *(*PackInFunc)(lua_State *L, int idx);
+/*
+ * Unpacking callback: Turns a void* back into the Lua value (left on top of stack)
+ *
+ * Note that this function must take ownership of the pointer, so make sure
+ * to free or keep the memory.
+ * `L` can be nullptr to indicate that data should just be discarded.
+ */
+typedef void (*PackOutFunc)(lua_State *L, void *ptr);
+/*
+ * Register a packable type with the name of its metatable.
+ *
+ * Even though the callbacks are global this must be called for every Lua state
+ * that supports objects of this type.
+ * This function is thread-safe.
+ */
+void script_register_packer(lua_State *L, const char *regname,
+ PackInFunc fin, PackOutFunc fout);
+
+// Pack a Lua value
+PackedValue *script_pack(lua_State *L, int idx);
+// Unpack a Lua value (left on top of stack)
+// Note that this may modify the PackedValue, reusability is not guaranteed!
+void script_unpack(lua_State *L, PackedValue *val);
+
+// Dump contents of PackedValue to stdout for debugging
+void script_dump_packed(const PackedValue *val);