From 24f0eb6bc190476d43614eacdf7c23d7aa47b885 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Fri, 9 Feb 2024 17:17:16 +0100 Subject: [PATCH 1/8] Try to preserve metatable information in serialzed data --- builtin/common/serialize.lua | 71 ++++++++++++++++--- builtin/common/tests/serialize_spec.lua | 68 +++++++++++++++++- builtin/common/vector.lua | 4 ++ doc/lua_api.md | 2 +- .../mods/unittests/itemstack_equals.lua | 7 ++ 5 files changed, 140 insertions(+), 12 deletions(-) diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index 146128e0c..8a7974853 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -8,12 +8,23 @@ local next, rawget, pairs, pcall, error, type, setfenv, loadstring local table_concat, string_dump, string_format, string_match, math_huge = table.concat, string.dump, string.format, string.match, math.huge --- Recursively counts occurrences of objects (non-primitives including strings) in a table. -local function count_objects(value) +local itemstack_mt +if ItemStack then + itemstack_mt = getmetatable(ItemStack()) +end +local function is_itemstack(x) + return itemstack_mt and getmetatable(x) == itemstack_mt +end + +-- Recursively +-- (1) reads metatables from tables; +-- (2) counts occurrences of objects (non-primitives including strings) in a table. +local function prepare_objects(value) local counts = {} + local type_lookup = {} if value == nil then -- Early return for nil; tables can't contain nil - return counts + return counts, type_lookup end local function count_values(val) local type_ = type(val) @@ -22,19 +33,23 @@ local function count_objects(value) end local count = counts[val] counts[val] = (count or 0) + 1 + local mt = getmetatable(val) if type_ == "table" then if not count then for k, v in pairs(val) do count_values(k) count_values(v) end + if mt then + type_lookup[val] = core.known_metatables[mt] + end end - elseif type_ ~= "string" and type_ ~= "function" then + elseif type_ ~= "string" and type_ ~= "function" and not is_itemstack(val) then error("unsupported type: " .. type_) end end - count_values(value) - return counts + count_values(value, {}) + return counts, type_lookup end -- Build a "set" of Lua keywords. These can't be used as short key names. @@ -58,6 +73,10 @@ local function dump_func(func) return string_format("loadstring(%q)", string_dump(func)) end +local function dump_itemstack(item) + return string_format("ItemStack(%q)", item:to_string()) +end + -- Serializes Lua nil, booleans, numbers, strings, tables and even functions -- Tables are referenced by reference, strings are referenced by value. Supports circular tables. local function serialize(value, write) @@ -66,7 +85,11 @@ local function serialize(value, write) local references = {} -- Circular tables that must be filled using `table[key] = value` statements local to_fill = {} - for object, count in pairs(count_objects(value)) do + local counts, typenames = prepare_objects(value) + if next(typenames) then + write "if not setmetatable then core={known_metatables={}}; setmetatable = function(x) return x end; end;" + end + for object, count in pairs(counts) do local type_ = type(object) -- Object must appear more than once. If it is a string, the reference has to be shorter than the string. if count >= 2 and (type_ ~= "string" or #reference + 5 < #object) then @@ -82,10 +105,12 @@ local function serialize(value, write) write(dump_func(object)) elseif type_ == "string" then write(quote(object)) + elseif is_itemstack(object) then + write(dump_itemstack(object)) end write(";") references[object] = reference - if type_ == "table" then + if type_ ~= "string" and not is_itemstack(object) then to_fill[object] = reference end refnum = refnum + 1 @@ -96,7 +121,7 @@ local function serialize(value, write) local function use_short_key(key) return not references[key] and type(key) == "string" and (not keywords[key]) and string_match(key, "^[%a_][%a%d_]*$") end - local function dump(value) + local function dump(value, skip_mt) -- Primitive types if value == nil then return write("nil") @@ -126,12 +151,23 @@ local function serialize(value, write) write(ref) return write"]" end + if (not skip_mt) and typenames[value] then + write "setmetatable(" + dump(value, true) + write ",core.known_metatables[" + dump(typenames[value]) + write "] or {})" + return + end if type_ == "string" then return write(quote(value)) end if type_ == "function" then return write(dump_func(value)) end + if is_itemstack(value) then + return write(dump_itemstack(value)) + end if type_ == "table" then write("{") -- First write list keys: @@ -169,6 +205,7 @@ local function serialize(value, write) end -- Write the statements to fill circular tables for table, ref in pairs(to_fill) do + local typename = typenames[table] for k, v in pairs(table) do write("_[") write(ref) @@ -185,6 +222,13 @@ local function serialize(value, write) dump(v) write(";") end + if typename then + write("setmetatable(_[") + write(ref) + write("],core.known_metatables[") + dump(typename) + write("] or {})") + end end write("return ") dump(value) @@ -216,7 +260,13 @@ function core.deserialize(str, safe) if not func then return nil, err end -- math.huge was serialized to inf and NaNs to nan by Lua in engine version 5.6, so we have to support this here - local env = {inf = math_huge, nan = 0/0} + local env = { + inf = math_huge, + nan = 0/0, + ItemStack = ItemStack or function(str) return str end, + setmetatable = setmetatable, + core = { known_metatables = core.known_metatables } + } if safe then env.loadstring = dummy_func else @@ -236,3 +286,4 @@ function core.deserialize(str, safe) end return nil, value_or_err end + diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua index d4e501468..5037976ad 100644 --- a/builtin/common/tests/serialize_spec.lua +++ b/builtin/common/tests/serialize_spec.lua @@ -4,6 +4,7 @@ _G.setfenv = require 'busted.compatibility'.setfenv dofile("builtin/common/serialize.lua") dofile("builtin/common/vector.lua") +dofile("builtin/common/metatable.lua") -- Supports circular tables; does not support table keys -- Correctly checks whether a mapping of references ("same") exists @@ -40,11 +41,32 @@ local t1, t2 = {x, x, y, y}, {x, y, x, y} assert.same(t1, t2) -- will succeed because it only checks whether the depths match assert(not pcall(assert_same, t1, t2)) -- will correctly fail because it checks whether the refs match +local pair_mt = { + __eq = function(x, y) + return x[1] == y[1] and x[2] == y[2] + end, +} +local function pair(x, y) + return setmetatable({x, y}, pair_mt) +end +-- Use our own serialization functions to avoid incorrectly passing test related to references. +core.register_metatable("pair", pair_mt) +assert.equals(pair(1, 2), pair(1, 2)) +assert.not_equals(pair(1, 2), pair(3, 4)) + describe("serialize", function() local function assert_preserves(value) local preserved_value = core.deserialize(core.serialize(value)) assert_same(value, preserved_value) end + local function assert_strictly_preserves(value) + local preserved_value = core.deserialize(core.serialize(value)) + assert.equals(value, preserved_value) + end + local function assert_compatibly_preserves(value) + local preserved_value = loadstring(core.serialize(value))() + assert_same(value, preserved_value) + end it("works", function() assert_preserves({cat={sound="nyan", speed=400}, dog={sound="woof"}}) end) @@ -53,6 +75,10 @@ describe("serialize", function() assert_preserves({escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}) end) + it("handles nil", function() + assert_strictly_preserves(nil) + end) + it("handles NaN & infinities", function() local nan = core.deserialize(core.serialize(0/0)) assert(nan ~= nan) @@ -113,7 +139,10 @@ describe("serialize", function() it("vectors work", function() local v = vector.new(1, 2, 3) assert_preserves({v}) - assert_preserves(v) + assert_compatibly_preserves({v}) + assert_strictly_preserves(v) + assert_compatibly_preserves(v) + assert(core.deserialize(core.serialize(v)):check()) -- abuse v = vector.new(1, 2, 3) @@ -121,6 +150,43 @@ describe("serialize", function() assert_preserves(v) end) + it("correctly handles typed objects with multiple references", function() + local x, y = pair(1, 2), pair(1, 2) + local t = core.deserialize(core.serialize{x, x, y}) + assert.equals(x, t[1]) + assert.equals(x, t[3]) + assert(rawequal(t[1], t[2])) + assert(not rawequal(t[1], t[3])) + end) + + it("correctly handles recursive typed objects with the identity function as serializer", function() + local mt = { + __eq = function(x, y) + return x[1] == y[1] + end, + } + core.register_metatable("test_recursive_typed", mt) + local t = setmetatable({1}, mt) + t[2] = t + assert_strictly_preserves(t) + end) + + it("correctly handles binary trees", function() + local child = {pair(1, 1)} + local layers = 4 + for i = 2, layers do + child[i] = pair(child[i-1], child[i-1]) + end + local tree = child[layers] + assert_strictly_preserves(tree) + local node = core.deserialize(core.serialize(tree)) + for i = 2, layers do + assert(rawequal(node[1], node[2])) + node = node[1] + end + assert_compatibly_preserves(tree) + end) + it("handles keywords as keys", function() assert_preserves({["and"] = "keyword", ["for"] = "keyword"}) end) diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index 7a8558cbd..bbe4e6ad4 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -12,6 +12,10 @@ vector = {} local metatable = {} vector.metatable = metatable +if core and core.register_serializable then + core.register_serializable("__builtin:vector", metatable) +end + local xyz = {"x", "y", "z"} -- only called when rawget(v, key) returns nil diff --git a/doc/lua_api.md b/doc/lua_api.md index ba72e97e9..50a05f864 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7655,7 +7655,7 @@ Misc. * `core.register_portable_metatable(name, mt)`: * Register a metatable that should be preserved when Lua data is transferred - between environments (via IPC or `handle_async`). + between environments (via IPC, `handle_async`, or `core.serialize`). * `name` is a string that identifies the metatable. It is recommended to follow the `modname:name` convention for this identifier. * `mt` is the metatable to register. diff --git a/games/devtest/mods/unittests/itemstack_equals.lua b/games/devtest/mods/unittests/itemstack_equals.lua index 561e612c4..ff74e562b 100644 --- a/games/devtest/mods/unittests/itemstack_equals.lua +++ b/games/devtest/mods/unittests/itemstack_equals.lua @@ -72,3 +72,10 @@ local function test_itemstack_equals_metadata() end unittests.register("test_itemstack_equals_metadata", test_itemstack_equals_metadata) + +local function test_itemstack_serialization_preservation() + local i = ItemStack("basenodes:stone 20 1000") + assert(i:equals(core.deserialize(core.serialize(i)))) +end + +unittests.register("test_itemstack_serialization_preservation", test_itemstack_serialization_preservation) From 5f56d14bd4c251af8c3f8f6f7c85212d0ea94080 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Tue, 18 Feb 2025 07:37:03 +0100 Subject: [PATCH 2/8] minor --- builtin/common/tests/serialize_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua index 5037976ad..c3be4af53 100644 --- a/builtin/common/tests/serialize_spec.lua +++ b/builtin/common/tests/serialize_spec.lua @@ -50,7 +50,7 @@ local function pair(x, y) return setmetatable({x, y}, pair_mt) end -- Use our own serialization functions to avoid incorrectly passing test related to references. -core.register_metatable("pair", pair_mt) +core.register_portable_metatable("pair", pair_mt) assert.equals(pair(1, 2), pair(1, 2)) assert.not_equals(pair(1, 2), pair(3, 4)) @@ -165,7 +165,7 @@ describe("serialize", function() return x[1] == y[1] end, } - core.register_metatable("test_recursive_typed", mt) + core.register_portable_metatable("test_recursive_typed", mt) local t = setmetatable({1}, mt) t[2] = t assert_strictly_preserves(t) From e713d5291dfafa303406d580126ec85f1b532468 Mon Sep 17 00:00:00 2001 From: y5nw Date: Sat, 22 Feb 2025 21:22:14 +0100 Subject: [PATCH 3/8] minor refactor --- builtin/common/serialize.lua | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index 8a7974853..aa1a5c0a7 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -26,29 +26,29 @@ local function prepare_objects(value) -- Early return for nil; tables can't contain nil return counts, type_lookup end - local function count_values(val) + local function count_values(val, recount) local type_ = type(val) if type_ == "boolean" or type_ == "number" then return end - local count = counts[val] - counts[val] = (count or 0) + 1 - local mt = getmetatable(val) - if type_ == "table" then - if not count then - for k, v in pairs(val) do - count_values(k) - count_values(v) - end - if mt then - type_lookup[val] = core.known_metatables[mt] - end + if not recount then + local count = counts[val] + counts[val] = (count or 0) + 1 + end + local mt = (not recount) and (type_ == "table" or type_ == "userdata") and getmetatable(val) + if mt and core.known_metatables[mt] then + type_lookup[val] = core.known_metatables[mt] + count_values(val, true) + elseif type_ == "table" then + for k, v in pairs(val) do + count_values(k) + count_values(v) end - elseif type_ ~= "string" and type_ ~= "function" and not is_itemstack(val) then + elseif type_ ~= "string" then error("unsupported type: " .. type_) end end - count_values(value, {}) + count_values(value, false) return counts, type_lookup end From 738b3cfadc508e85794ebefadff456204ec08428 Mon Sep 17 00:00:00 2001 From: y5nw Date: Sat, 22 Feb 2025 21:47:00 +0100 Subject: [PATCH 4/8] fixup --- builtin/common/serialize.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index aa1a5c0a7..221ac66fd 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -31,24 +31,26 @@ local function prepare_objects(value) if type_ == "boolean" or type_ == "number" then return end + local count = counts[val] if not recount then - local count = counts[val] counts[val] = (count or 0) + 1 end - local mt = (not recount) and (type_ == "table" or type_ == "userdata") and getmetatable(val) + local mt = (not count) and (type_ == "table" or type_ == "userdata") and getmetatable(val) if mt and core.known_metatables[mt] then type_lookup[val] = core.known_metatables[mt] count_values(val, true) elseif type_ == "table" then - for k, v in pairs(val) do - count_values(k) - count_values(v) + if recount or not count then + for k, v in pairs(val) do + count_values(k) + count_values(v) + end end - elseif type_ ~= "string" then + elseif type_ ~= "string" and type_ ~= "function" then error("unsupported type: " .. type_) end end - count_values(value, false) + count_values(value) return counts, type_lookup end From 2a1a2b1f9d6cbb6826f472b3dba218b809db69cb Mon Sep 17 00:00:00 2001 From: y5nw Date: Sat, 22 Feb 2025 22:40:34 +0100 Subject: [PATCH 5/8] Add API for custom (de)serializers --- builtin/common/metatable.lua | 16 ++++++++- builtin/common/serialize.lua | 69 ++++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/builtin/common/metatable.lua b/builtin/common/metatable.lua index debc4d3c5..b9a567ab2 100644 --- a/builtin/common/metatable.lua +++ b/builtin/common/metatable.lua @@ -1,14 +1,28 @@ -- Registered metatables, used by the C++ packer +local serializable_metatables = {} local known_metatables = {} -function core.register_portable_metatable(name, mt) + +local function dummy_serializer(x) + return x +end + +function core.register_portable_metatable(name, mt, serializer, deserializer) + serializer = serializer or dummy_serializer + deserializer = deserializer or function(x) return setmetatable(x, mt) end assert(type(name) == "string", ("attempt to use %s value as metatable name"):format(type(name))) assert(type(mt) == "table", ("attempt to register a %s value as metatable"):format(type(mt))) + assert(type(serializer), ("attempt to use a %s value as serializer"):format(type(serializer))) + assert(type(deserializer), ("attempt to use a %s value as serialier"):format(type(deserializer))) assert(known_metatables[name] == nil or known_metatables[name] == mt, ("attempt to override metatable %s"):format(name)) known_metatables[name] = mt known_metatables[mt] = name + serializable_metatables[mt] = serializer + serializable_metatables[name] = deserializer end + core.known_metatables = known_metatables +core.serializable_metatables = serializable_metatables function core.register_async_metatable(...) core.log("deprecated", "core.register_async_metatable is deprecated. " .. diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index 221ac66fd..58765e5e6 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -16,6 +16,10 @@ local function is_itemstack(x) return itemstack_mt and getmetatable(x) == itemstack_mt end +local function pack_args(...) + return {n = select("#", ...), ...} +end + -- Recursively -- (1) reads metatables from tables; -- (2) counts occurrences of objects (non-primitives including strings) in a table. @@ -36,9 +40,12 @@ local function prepare_objects(value) counts[val] = (count or 0) + 1 end local mt = (not count) and (type_ == "table" or type_ == "userdata") and getmetatable(val) - if mt and core.known_metatables[mt] then - type_lookup[val] = core.known_metatables[mt] - count_values(val, true) + if mt and core.serializable_metatables[mt] then + local args = pack_args(core.known_metatables[mt], core.serializable_metatables[mt](val)) + type_lookup[val] = args + for _, v in ipairs(args) do + count_values(v, rawequal(v, val)) + end elseif type_ == "table" then if recount or not count then for k, v in pairs(val) do @@ -87,9 +94,13 @@ local function serialize(value, write) local references = {} -- Circular tables that must be filled using `table[key] = value` statements local to_fill = {} - local counts, typenames = prepare_objects(value) - if next(typenames) then - write "if not setmetatable then core={known_metatables={}}; setmetatable = function(x) return x end; end;" + local counts, typeinfo = prepare_objects(value) + if next(typeinfo) then + write [[ + if not (core and core.serializable_metatables) then + core = { known_metatables = {}, serializable_metatables = {}} + end; + ]] end for object, count in pairs(counts) do local type_ = type(object) @@ -123,7 +134,22 @@ local function serialize(value, write) local function use_short_key(key) return not references[key] and type(key) == "string" and (not keywords[key]) and string_match(key, "^[%a_][%a%d_]*$") end - local function dump(value, skip_mt) + local dump + local function dump_serialized(value) + local serialized = assert(typeinfo[value]) + write "(core.serializable_metatables[" + dump(serialized[1]) + write "])(" + for k = 2, serialized.n do + if k ~= 2 then + write "," + end + local v = serialized[k] + dump(v, rawequal(v, value)) + end + write ")" + end + dump = function(value, skip_mt) -- Primitive types if value == nil then return write("nil") @@ -153,12 +179,8 @@ local function serialize(value, write) write(ref) return write"]" end - if (not skip_mt) and typenames[value] then - write "setmetatable(" - dump(value, true) - write ",core.known_metatables[" - dump(typenames[value]) - write "] or {})" + if (not skip_mt) and typeinfo[value] then + dump_serialized(value) return end if type_ == "string" then @@ -206,9 +228,8 @@ local function serialize(value, write) end end -- Write the statements to fill circular tables - for table, ref in pairs(to_fill) do - local typename = typenames[table] - for k, v in pairs(table) do + for tbl, ref in pairs(to_fill) do + for k, v in pairs(tbl) do write("_[") write(ref) write("]") @@ -224,12 +245,12 @@ local function serialize(value, write) dump(v) write(";") end - if typename then - write("setmetatable(_[") + if typeinfo[tbl] then + write("_[") write(ref) - write("],core.known_metatables[") - dump(typename) - write("] or {})") + write("]=") + dump_serialized(tbl) + write(";") end end write("return ") @@ -266,8 +287,10 @@ function core.deserialize(str, safe) inf = math_huge, nan = 0/0, ItemStack = ItemStack or function(str) return str end, - setmetatable = setmetatable, - core = { known_metatables = core.known_metatables } + core = { + known_metatables = core.known_metatables, + serializable_metatables = core.serializable_metatables, + }, } if safe then env.loadstring = dummy_func From 54b14c6c62f41ed26c4e6d06d203e769e3f84114 Mon Sep 17 00:00:00 2001 From: y5nw Date: Sat, 22 Feb 2025 22:46:44 +0100 Subject: [PATCH 6/8] itemstack --- builtin/common/metatable.lua | 6 ++++++ builtin/common/serialize.lua | 19 +------------------ 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/builtin/common/metatable.lua b/builtin/common/metatable.lua index b9a567ab2..ccfa7c5c3 100644 --- a/builtin/common/metatable.lua +++ b/builtin/common/metatable.lua @@ -31,3 +31,9 @@ function core.register_async_metatable(...) end core.register_portable_metatable("__builtin:vector", vector.metatable) + +if ItemStack then + local item = ItemStack() + local itemstack_mt = getmetatable(item) + core.register_portable_metatable("__itemstack", itemstack_mt, item.to_table, ItemStack) +end diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index 58765e5e6..3fffcfddc 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -8,14 +8,6 @@ local next, rawget, pairs, pcall, error, type, setfenv, loadstring local table_concat, string_dump, string_format, string_match, math_huge = table.concat, string.dump, string.format, string.match, math.huge -local itemstack_mt -if ItemStack then - itemstack_mt = getmetatable(ItemStack()) -end -local function is_itemstack(x) - return itemstack_mt and getmetatable(x) == itemstack_mt -end - local function pack_args(...) return {n = select("#", ...), ...} end @@ -82,10 +74,6 @@ local function dump_func(func) return string_format("loadstring(%q)", string_dump(func)) end -local function dump_itemstack(item) - return string_format("ItemStack(%q)", item:to_string()) -end - -- Serializes Lua nil, booleans, numbers, strings, tables and even functions -- Tables are referenced by reference, strings are referenced by value. Supports circular tables. local function serialize(value, write) @@ -118,12 +106,10 @@ local function serialize(value, write) write(dump_func(object)) elseif type_ == "string" then write(quote(object)) - elseif is_itemstack(object) then - write(dump_itemstack(object)) end write(";") references[object] = reference - if type_ ~= "string" and not is_itemstack(object) then + if type_ ~= "string" then to_fill[object] = reference end refnum = refnum + 1 @@ -189,9 +175,6 @@ local function serialize(value, write) if type_ == "function" then return write(dump_func(value)) end - if is_itemstack(value) then - return write(dump_itemstack(value)) - end if type_ == "table" then write("{") -- First write list keys: From 9db64d5834df72e9f6c02f45c30ef40985bd00c7 Mon Sep 17 00:00:00 2001 From: y5nw Date: Sat, 22 Feb 2025 23:01:44 +0100 Subject: [PATCH 7/8] doc --- doc/lua_api.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 50a05f864..cd022946b 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7653,12 +7653,18 @@ Misc. * `core.global_exists(name)` * Checks if a global variable has been set, without triggering a warning. -* `core.register_portable_metatable(name, mt)`: +* `core.register_portable_metatable(name, mt, serializer, deserializer)`: * Register a metatable that should be preserved when Lua data is transferred between environments (via IPC, `handle_async`, or `core.serialize`). * `name` is a string that identifies the metatable. It is recommended to follow the `modname:name` convention for this identifier. * `mt` is the metatable to register. + * `serializer` is a function used by `core.serialize` to serialize data with + the given metatable. It may return multiple values, but the return values should not + contain the input datum unless `serializer` is the identity function. The default + value for `serializer` is the identity function. + * `deserializer` is a function used by `core.deserialize` to deserialize data from + values returned by `serializer`. The default value is a wrapper around `setmetatable`. * Note that the same metatable can be registered under multiple names, but multiple metatables must not be registered under the same name. * You must register the metatable in both the main environment From aa5d8aee1e71e8f53733bb9bfcc60384fa165ab0 Mon Sep 17 00:00:00 2001 From: y5nw Date: Sat, 22 Feb 2025 23:10:49 +0100 Subject: [PATCH 8/8] minor --- builtin/common/serialize.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index 3fffcfddc..5577cf991 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -109,7 +109,7 @@ local function serialize(value, write) end write(";") references[object] = reference - if type_ ~= "string" then + if type_ == "table" then to_fill[object] = reference end refnum = refnum + 1 @@ -269,7 +269,6 @@ function core.deserialize(str, safe) local env = { inf = math_huge, nan = 0/0, - ItemStack = ItemStack or function(str) return str end, core = { known_metatables = core.known_metatables, serializable_metatables = core.serializable_metatables,