Use tabs not spaces

This commit is contained in:
Lars Mueller 2021-07-14 11:51:47 +02:00
parent a86b24eef5
commit daa0251632
7 changed files with 395 additions and 394 deletions

@ -44,21 +44,21 @@ Binary Lua object notation. **Experimental.** Handling of subnormal numbers (ver
```lua
def = {
aux_is_valid = function(object)
return is_valid
end,
aux_len = function(object)
return length_in_bytes
end,
-- read type byte, stream providing :read(count), map of references -> id
aux_read = function(type, stream, references)
... = stream:read(...)
return object
end,
-- object to be written, stream providing :write(text), list of references
aux_write = function(object, stream, references)
stream:write(...)
end
aux_is_valid = function(object)
return is_valid
end,
aux_len = function(object)
return length_in_bytes
end,
-- read type byte, stream providing :read(count), map of references -> id
aux_read = function(type, stream, references)
... = stream:read(...)
return object
end,
-- object to be written, stream providing :write(text), list of references
aux_write = function(object, stream, references)
stream:write(...)
end
}
```
@ -128,22 +128,22 @@ assert(modlib.bluon:read(object, rope) == true)
```lua
-- Serializes all userdata to a constant string:
local custom_bluon = bluon.new{
aux_is_valid = function(object)
return type(object) == "userdata"
end,
aux_len = function(object)
return 1 + ("userdata"):len())
end,
aux_read = function(type, stream, references)
assert(type == 100, "unsupported type")
assert(stream:read(("userdata"):len()) == "userdata")
return userdata()
end,
-- object to be written, stream providing :write(text), list of references
aux_write = function(object, stream, references)
assert(type(object) == "userdata")
stream:write"\100userdata"
end
aux_is_valid = function(object)
return type(object) == "userdata"
end,
aux_len = function(object)
return 1 + ("userdata"):len())
end,
aux_read = function(type, stream, references)
assert(type == 100, "unsupported type")
assert(stream:read(("userdata"):len()) == "userdata")
return userdata()
end,
-- object to be written, stream providing :write(text), list of references
aux_write = function(object, stream, references)
assert(type(object) == "userdata")
stream:write"\100userdata"
end
}
-- Write to text
local rope = modlib.table.rope{}
@ -255,21 +255,21 @@ A schematic format with support for metadata and baked light data. **Experimenta
* Fixes
* Colorspec
* Stricter patterns in `colorspec:from_string`
* `colorspec:to_string` works now
* Stricter patterns in `colorspec:from_string`
* `colorspec:to_string` works now
* Lua log file
* Patched memory leaks
* Added option to not reference strings
* Handling of circular tables
* Patched memory leaks
* Added option to not reference strings
* Handling of circular tables
* Additions
* `luon`: Configurable serialization to and from Lua with circular and string reference support
* `minetest.luon` even supports `ItemStack` and `AreaStore`
* `minetest.luon` even supports `ItemStack` and `AreaStore`
* `vector.rotate3`
* `table.deep_foreach_any`
* `func`:
* `func.iterate`
* `func.aggregate`
* Functional wrappers for operators
* `func.iterate`
* `func.aggregate`
* Functional wrappers for operators
* Improvements
* Code quality
* Performance

@ -9,82 +9,82 @@ list.metatable = metatable
-- Takes a list
function list:new()
self.head = 0
self.length = #self
return setmetatable(self, metatable)
self.head = 0
self.length = #self
return setmetatable(self, metatable)
end
function list:in_bounds(index)
return index >= 1 and index <= self.length
return index >= 1 and index <= self.length
end
function list:get(index)
return self[self.head + index]
return self[self.head + index]
end
function list:set(index, value)
assert(value ~= nil)
self[self.head + index] = value
assert(value ~= nil)
self[self.head + index] = value
end
function list:len()
return self.length
return self.length
end
function list:ipairs()
local index = 1
return function()
if index > self.length then
return
end
return index, self[self.head + index]
end
local index = 1
return function()
if index > self.length then
return
end
return index, self[self.head + index]
end
end
function list:rpairs()
local index = self.length
return function()
if index < 1 then
return
end
return index, self[self.head + index]
end
local index = self.length
return function()
if index < 1 then
return
end
return index, self[self.head + index]
end
end
function list:push_tail(value)
assert(value ~= nil)
self.length = self.length + 1
self[self.head + self.length] = value
assert(value ~= nil)
self.length = self.length + 1
self[self.head + self.length] = value
end
function list:get_tail()
return self[self.head + self.length]
return self[self.head + self.length]
end
function list:pop_tail()
if self.length == 0 then return end
local value = self:get_tail()
self[self.head + self.length] = nil
self.length = self.length - 1
return value
if self.length == 0 then return end
local value = self:get_tail()
self[self.head + self.length] = nil
self.length = self.length - 1
return value
end
function list:push_head(value)
self[self.head] = value
self.head = self.head - 1
self.length = self.length + 1
self[self.head] = value
self.head = self.head - 1
self.length = self.length + 1
end
function list:get_head()
return self[self.head + 1]
return self[self.head + 1]
end
function list:pop_head()
if self.length == 0 then return end
local value = self:get_head()
self.head = self.head + 1
self[self.head] = nil
return value
if self.length == 0 then return end
local value = self:get_head()
self.head = self.head + 1
self[self.head] = nil
return value
end
return list

279
luon.lua

@ -1,16 +1,16 @@
local assert, next, pairs, pcall, error, type, table_insert, table_concat, string_format, string_match, setmetatable, select, setfenv, math_huge, loadfile, loadstring
= assert, next, pairs, pcall, error, type, table.insert, table.concat, string.format, string.match, setmetatable, select, setfenv, math.huge, loadfile, loadstring
= assert, next, pairs, pcall, error, type, table.insert, table.concat, string.format, string.match, setmetatable, select, setfenv, math.huge, loadfile, loadstring
local count_objects = modlib.table.count_objects
-- Build a table with the succeeding character from A-Z
local succ = {}
for char = ("A"):byte(), ("Z"):byte() - 1 do
succ[string.char(char)] = string.char(char + 1)
succ[string.char(char)] = string.char(char + 1)
end
local function quote(string)
return string_format("%q", string)
return string_format("%q", string)
end
local _ENV = {}
@ -19,169 +19,170 @@ local metatable = {__index = _ENV}
_ENV.metatable = metatable
function new(self)
return setmetatable(self, metatable)
return setmetatable(self, metatable)
end
function aux_write(_self, _object)
-- returns reader, arguments
return
-- returns reader, arguments
return
end
aux_read = {}
function write(self, value, write)
local reference = {"A"}
local function increment_reference(place)
if not reference[place] then
reference[place] = "B"
elseif reference[place] == "Z" then
reference[place] = "A"
return increment_reference(place + 1)
else
reference[place] = succ[reference[place]]
end
end
local references = {}
local to_fill = {}
-- TODO sort objects by count, give frequently referenced objects shorter references
for object, count in pairs(count_objects(value)) do
local type_ = type(object)
if count >= 2 and (type_ ~= "string" or #reference + 2 < #object) then
local ref = table_concat(reference)
write(ref)
write"="
write(type_ == "table" and "{}" or quote(object))
write";"
references[object] = ref
if type_ == "table" then
to_fill[object] = ref
end
increment_reference(1)
end
end
local function is_short_key(key)
return not references[key] and type(key) == "string" and string_match(key, "^[%a_][%a%d_]*$")
end
local function dump(value)
-- Primitive types
if value == nil then
return write"nil"
end
if value == true then
return write"true"
end
if value == false then
return write"false"
end
local type_ = type(value)
if type_ == "number" then
return write(string_format("%.17g", value))
end
-- Reference types: table and string
local ref = references[value]
if ref then
-- Referenced
return write(ref)
elseif type_ == "string" then
return write(quote(value))
elseif type_ == "table" then
local first = true
write"{"
local len = #value
for i = 1, len do
if not first then write";" end
dump(value[i])
first = false
end
for k, v in next, value do
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
if not first then write";" end
if is_short_key(k) then
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
first = false
end
end
write"}"
else
-- TODO move aux_write to start, to allow dealing with metatables etc.?
(function(func, ...)
-- functions are the only way to deal with varargs
if not func then
return error("unsupported type: " .. type_)
end
write(func)
write"("
local n = select("#", ...)
local args = {...}
for i = 1, n - 1 do
dump(args[i])
write","
end
if n > 0 then
write(args[n])
end
write")"
end)(self:aux_write(value))
end
end
for table, ref in pairs(to_fill) do
for k, v in pairs(table) do
write(ref)
if is_short_key(k) then
write"."
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
write";"
end
end
write"return "
dump(value)
local reference = {"A"}
local function increment_reference(place)
if not reference[place] then
reference[place] = "B"
elseif reference[place] == "Z" then
reference[place] = "A"
return increment_reference(place + 1)
else
reference[place] = succ[reference[place]]
end
end
local references = {}
local to_fill = {}
-- TODO sort objects by count, give frequently referenced objects shorter references
for object, count in pairs(count_objects(value)) do
local type_ = type(object)
if count >= 2 and (type_ ~= "string" or #reference + 2 < #object) then
local ref = table_concat(reference)
write(ref)
write"="
write(type_ == "table" and "{}" or quote(object))
write";"
references[object] = ref
if type_ == "table" then
to_fill[object] = ref
end
increment_reference(1)
end
end
local function is_short_key(key)
return not references[key] and type(key) == "string" and string_match(key, "^[%a_][%a%d_]*$")
end
local function dump(value)
-- Primitive types
if value == nil then
return write"nil"
end
if value == true then
return write"true"
end
if value == false then
return write"false"
end
local type_ = type(value)
if type_ == "number" then
return write(string_format("%.17g", value))
end
-- Reference types: table and string
local ref = references[value]
if ref then
-- Referenced
return write(ref)
elseif type_ == "string" then
return write(quote(value))
elseif type_ == "table" then
local first = true
write"{"
local len = #value
for i = 1, len do
if not first then write";" end
dump(value[i])
first = false
end
for k, v in next, value do
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
if not first then write";" end
if is_short_key(k) then
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
first = false
end
end
write"}"
else
-- TODO move aux_write to start, to allow dealing with metatables etc.?
(function(func, ...)
-- functions are the only way to deal with varargs
if not func then
return error("unsupported type: " .. type_)
end
write(func)
write"("
local n = select("#", ...)
local args = {...}
for i = 1, n - 1 do
dump(args[i])
write","
end
if n > 0 then
write(args[n])
end
write")"
end)(self:aux_write(value))
end
end
for table, ref in pairs(to_fill) do
for k, v in pairs(table) do
write(ref)
if is_short_key(k) then
write"."
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
write";"
end
end
write"return "
dump(value)
end
function write_file(self, value, file)
return self:write(value, function(text)
file:write(text)
end)
return self:write(value, function(text)
file:write(text)
end)
end
function write_string(self, value)
local rope = {}
self:write(value, function(text)
table_insert(rope, text)
end)
return table_concat(rope)
local rope = {}
self:write(value, function(text)
table_insert(rope, text)
end)
return table_concat(rope)
end
function read(self, ...)
local read = assert(...)
-- math.huge is serialized to inf, 0/0 is serialized to -nan
-- TODO verify this is actually the case, see https://en.wikipedia.org/wiki/Printf_format_string
setfenv(read, setmetatable({inf = math_huge, nan = 0/0}, {__index = self.aux_read}))
local success, value_or_err = pcall(read)
if success then
return value_or_err
end
return nil, value_or_err
if success then
return value_or_err
end
return nil, value_or_err
end
function read_file(self, path)
return self:read(loadfile(path))
return self:read(loadfile(path))
end
function read_string(self, string)
return self:read(loadstring(string))
return self:read(loadstring(string))
end
return _ENV

@ -1,6 +1,6 @@
local _ENV = setmetatable({}, {__index = _G})
local function load(filename)
assert(loadfile(modlib.mod.get_resource(modlib.modname, "minetest", filename .. ".lua")))(_ENV)
assert(loadfile(modlib.mod.get_resource(modlib.modname, "minetest", filename .. ".lua")))(_ENV)
end
load"misc"
load"collisionboxes"

@ -1,29 +1,29 @@
-- Localize globals
local getmetatable, AreaStore, ItemStack
= getmetatable, AreaStore, ItemStack
= getmetatable, AreaStore, ItemStack
-- Metatable lookup for classes specified in lua_api.txt, section "Class reference"
local AreaStoreMT = getmetatable(AreaStore())
local ItemStackMT = getmetatable(ItemStack"")
local metatables = {
[AreaStoreMT] = {name = "AreaStore", method = AreaStoreMT.to_string},
[ItemStackMT] = {name = "ItemStack", method = ItemStackMT.to_table},
-- TODO expand
[AreaStoreMT] = {name = "AreaStore", method = AreaStoreMT.to_string},
[ItemStackMT] = {name = "ItemStack", method = ItemStackMT.to_table},
-- TODO expand
}
(...).luon = modlib.luon.new{
aux_write = function(_, value)
local type = metatables[getmetatable(value)]
if type then
return type.name, type.method(value)
end
end,
aux_read = {
AreaStore = function(...)
local store = AreaStore()
store:from_string(...)
return store
end,
ItemStack = ItemStack
}
aux_write = function(_, value)
local type = metatables[getmetatable(value)]
if type then
return type.name, type.method(value)
end
end,
aux_read = {
AreaStore = function(...)
local store = AreaStore()
store:from_string(...)
return store
end,
ItemStack = ItemStack
}
}

@ -1,6 +1,6 @@
-- Localize globals
local VoxelArea, ItemStack, assert, error, io, ipairs, math, minetest, modlib, next, pairs, setmetatable, string, table, type, vector
= VoxelArea, ItemStack, assert, error, io, ipairs, math, minetest, modlib, next, pairs, setmetatable, string, table, type, vector
= VoxelArea, ItemStack, assert, error, io, ipairs, math, minetest, modlib, next, pairs, setmetatable, string, table, type, vector
-- Set environment
local _ENV = ...
@ -10,179 +10,179 @@ schematic = {}
local metatable = {__index = schematic}
function schematic.setmetatable(self)
return setmetatable(self, metatable)
return setmetatable(self, metatable)
end
function schematic.create(self, pos_min, pos_max)
self.size = vector.subtract(pos_max, pos_min)
local voxelmanip = minetest.get_voxel_manip(pos_min, pos_max)
local emin, emax = voxelmanip:read_from_map(pos_min, pos_max)
local voxelarea = VoxelArea:new{ MinEdge = emin, MaxEdge = emax }
local nodes, light_values, param2s = {}, self.light_values and {}, {}
local vm_nodes, vm_light_values, vm_param2s = voxelmanip:get_data(), light_values and voxelmanip:get_light_data(), voxelmanip:get_param2_data()
local node_names, node_ids = {}, {}
local i = 0
for index in voxelarea:iterp(pos_min, pos_max) do
if nodes[index] == minetest.CONTENT_UNKNOWN or nodes[index] == minetest.CONTENT_IGNORE then
error("unknown or ignore node at " .. minetest.pos_to_string(voxelarea:position(index)))
end
local name = minetest.get_name_from_content_id(vm_nodes[index])
local id = node_ids[name]
if not id then
table.insert(node_names, name)
id = #node_names
node_ids[name] = id
end
i = i + 1
nodes[i] = id
if self.light_values then
light_values[i] = vm_light_values[index]
end
param2s[i] = vm_param2s[index]
end
local metas = self.metas
if metas or metas == nil then
local indexing = vector.add(self.size, 1)
metas = {}
for _, pos in ipairs(minetest.find_nodes_with_meta(pos_min, pos_max)) do
local meta = minetest.get_meta(pos):to_table()
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
local relative = vector.subtract(pos, pos_min)
metas[((relative.z * indexing.y) + relative.y) * indexing.x + relative.x] = meta
end
end
end
self.node_names = node_names
self.nodes = nodes
self.light_values = light_values
self.param2s = param2s
self.metas = metas
return schematic.setmetatable(self)
self.size = vector.subtract(pos_max, pos_min)
local voxelmanip = minetest.get_voxel_manip(pos_min, pos_max)
local emin, emax = voxelmanip:read_from_map(pos_min, pos_max)
local voxelarea = VoxelArea:new{ MinEdge = emin, MaxEdge = emax }
local nodes, light_values, param2s = {}, self.light_values and {}, {}
local vm_nodes, vm_light_values, vm_param2s = voxelmanip:get_data(), light_values and voxelmanip:get_light_data(), voxelmanip:get_param2_data()
local node_names, node_ids = {}, {}
local i = 0
for index in voxelarea:iterp(pos_min, pos_max) do
if nodes[index] == minetest.CONTENT_UNKNOWN or nodes[index] == minetest.CONTENT_IGNORE then
error("unknown or ignore node at " .. minetest.pos_to_string(voxelarea:position(index)))
end
local name = minetest.get_name_from_content_id(vm_nodes[index])
local id = node_ids[name]
if not id then
table.insert(node_names, name)
id = #node_names
node_ids[name] = id
end
i = i + 1
nodes[i] = id
if self.light_values then
light_values[i] = vm_light_values[index]
end
param2s[i] = vm_param2s[index]
end
local metas = self.metas
if metas or metas == nil then
local indexing = vector.add(self.size, 1)
metas = {}
for _, pos in ipairs(minetest.find_nodes_with_meta(pos_min, pos_max)) do
local meta = minetest.get_meta(pos):to_table()
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
local relative = vector.subtract(pos, pos_min)
metas[((relative.z * indexing.y) + relative.y) * indexing.x + relative.x] = meta
end
end
end
self.node_names = node_names
self.nodes = nodes
self.light_values = light_values
self.param2s = param2s
self.metas = metas
return schematic.setmetatable(self)
end
function schematic:write_to_voxelmanip(voxelmanip, pos_min)
local pos_max = vector.add(pos_min, self.size)
local emin, emax = voxelmanip:read_from_map(pos_min, pos_max)
local voxelarea = VoxelArea:new{ MinEdge = emin, MaxEdge = emax }
local nodes, light_values, param2s, metas = self.nodes, self.light_values, self.param2s, self.metas
local vm_nodes, vm_lights, vm_param2s = voxelmanip:get_data(), light_values and voxelmanip:get_light_data(), voxelmanip:get_param2_data()
for _, pos in ipairs(minetest.find_nodes_with_meta(pos_min, pos_max)) do
-- Clear all metadata. Due to an engine bug, nodes will actually have empty metadata.
minetest.get_meta(pos):from_table{}
end
local content_ids = {}
for index, name in ipairs(self.node_names) do
content_ids[index] = assert(minetest.get_content_id(name), ("unknown node %q"):format(name))
end
local i = 0
for index in voxelarea:iterp(pos_min, pos_max) do
i = i + 1
vm_nodes[index] = content_ids[nodes[i]]
if light_values then
vm_lights[index] = light_values[i]
end
vm_param2s[index] = param2s[i]
end
voxelmanip:set_data(vm_nodes)
if light_values then
voxelmanip:set_light_data(vm_lights)
end
voxelmanip:set_param2_data(vm_param2s)
if metas then
local indexing = vector.add(self.size, 1)
for index, meta in pairs(metas) do
local floored = math.floor(index / indexing.x)
local relative = {
x = index % indexing.x,
y = floored % indexing.y,
z = math.floor(floored / indexing.y)
}
minetest.get_meta(vector.add(relative, pos_min)):from_table(meta)
end
end
local pos_max = vector.add(pos_min, self.size)
local emin, emax = voxelmanip:read_from_map(pos_min, pos_max)
local voxelarea = VoxelArea:new{ MinEdge = emin, MaxEdge = emax }
local nodes, light_values, param2s, metas = self.nodes, self.light_values, self.param2s, self.metas
local vm_nodes, vm_lights, vm_param2s = voxelmanip:get_data(), light_values and voxelmanip:get_light_data(), voxelmanip:get_param2_data()
for _, pos in ipairs(minetest.find_nodes_with_meta(pos_min, pos_max)) do
-- Clear all metadata. Due to an engine bug, nodes will actually have empty metadata.
minetest.get_meta(pos):from_table{}
end
local content_ids = {}
for index, name in ipairs(self.node_names) do
content_ids[index] = assert(minetest.get_content_id(name), ("unknown node %q"):format(name))
end
local i = 0
for index in voxelarea:iterp(pos_min, pos_max) do
i = i + 1
vm_nodes[index] = content_ids[nodes[i]]
if light_values then
vm_lights[index] = light_values[i]
end
vm_param2s[index] = param2s[i]
end
voxelmanip:set_data(vm_nodes)
if light_values then
voxelmanip:set_light_data(vm_lights)
end
voxelmanip:set_param2_data(vm_param2s)
if metas then
local indexing = vector.add(self.size, 1)
for index, meta in pairs(metas) do
local floored = math.floor(index / indexing.x)
local relative = {
x = index % indexing.x,
y = floored % indexing.y,
z = math.floor(floored / indexing.y)
}
minetest.get_meta(vector.add(relative, pos_min)):from_table(meta)
end
end
end
function schematic:place(pos_min)
local pos_max = vector.add(pos_min, self.size)
local voxelmanip = minetest.get_voxel_manip(pos_min, pos_max)
self:write_to_voxelmanip(voxelmanip, pos_min)
voxelmanip:write_to_map(not self.light_values)
return voxelmanip
local pos_max = vector.add(pos_min, self.size)
local voxelmanip = minetest.get_voxel_manip(pos_min, pos_max)
self:write_to_voxelmanip(voxelmanip, pos_min)
voxelmanip:write_to_map(not self.light_values)
return voxelmanip
end
local function table_to_byte_string(tab)
if not tab then return end
return table.concat(modlib.table.map(tab, string.char))
if not tab then return end
return table.concat(modlib.table.map(tab, string.char))
end
local function write_bluon(self, stream)
local metas, light_values, param2s = self.metas, self.light_values, self.param2s
self.metas = modlib.table.copy(metas)
for _, meta in pairs(self.metas) do
for _, list in pairs(meta.inventory) do
for index, stack in pairs(list) do
list[index] = stack:to_string()
end
end
end
self.light_values, self.param2s = table_to_byte_string(light_values), table_to_byte_string(param2s)
modlib.bluon:write(self, stream)
self.metas, self.light_values, self.param2s = metas, light_values, param2s
local metas, light_values, param2s = self.metas, self.light_values, self.param2s
self.metas = modlib.table.copy(metas)
for _, meta in pairs(self.metas) do
for _, list in pairs(meta.inventory) do
for index, stack in pairs(list) do
list[index] = stack:to_string()
end
end
end
self.light_values, self.param2s = table_to_byte_string(light_values), table_to_byte_string(param2s)
modlib.bluon:write(self, stream)
self.metas, self.light_values, self.param2s = metas, light_values, param2s
end
function schematic:write_bluon(path)
local file = io.open(path, "wb")
-- Header, short for "ModLib Bluon Schematic"
file:write"MLBS"
write_bluon(self, file)
file:close()
local file = io.open(path, "wb")
-- Header, short for "ModLib Bluon Schematic"
file:write"MLBS"
write_bluon(self, file)
file:close()
end
local function byte_string_to_table(self, field)
local byte_string = self[field]
if not byte_string then return end
local tab = {}
for i = 1, #byte_string do
tab[i] = byte_string:byte(i)
end
self[field] = tab
local byte_string = self[field]
if not byte_string then return end
local tab = {}
for i = 1, #byte_string do
tab[i] = byte_string:byte(i)
end
self[field] = tab
end
local function read_bluon(file)
local self = modlib.bluon:read(file)
assert(not file:read(1), "expected EOF")
for _, meta in pairs(self.metas) do
for _, list in pairs(meta.inventory) do
for index, itemstring in pairs(list) do
assert(type(itemstring) == "string")
list[index] = ItemStack(itemstring)
end
end
end
byte_string_to_table(self, "light_values")
byte_string_to_table(self, "param2s")
return self
local self = modlib.bluon:read(file)
assert(not file:read(1), "expected EOF")
for _, meta in pairs(self.metas) do
for _, list in pairs(meta.inventory) do
for index, itemstring in pairs(list) do
assert(type(itemstring) == "string")
list[index] = ItemStack(itemstring)
end
end
end
byte_string_to_table(self, "light_values")
byte_string_to_table(self, "param2s")
return self
end
function schematic.read_bluon(path)
local file = io.open(path, "rb")
assert(file:read(4) == "MLBS", "not a modlib bluon schematic")
return schematic.setmetatable(read_bluon(file))
local file = io.open(path, "rb")
assert(file:read(4) == "MLBS", "not a modlib bluon schematic")
return schematic.setmetatable(read_bluon(file))
end
function schematic:write_zlib_bluon(path, compression)
local file = io.open(path, "wb")
-- Header, short for "ModLib Zlib-compressed-bluon Schematic"
file:write"MLZS"
local rope = modlib.table.rope{}
write_bluon(self, rope)
local text = rope:to_text()
file:write(minetest.compress(text, "deflate", compression or 9))
file:close()
local file = io.open(path, "wb")
-- Header, short for "ModLib Zlib-compressed-bluon Schematic"
file:write"MLZS"
local rope = modlib.table.rope{}
write_bluon(self, rope)
local text = rope:to_text()
file:write(minetest.compress(text, "deflate", compression or 9))
file:close()
end
function schematic.read_zlib_bluon(path)
local file = io.open(path, "rb")
assert(file:read(4) == "MLZS", "not a modlib zlib compressed bluon schematic")
return schematic.setmetatable(read_bluon(modlib.text.inputstream(minetest.decompress(file:read"*a", "deflate"))))
local file = io.open(path, "rb")
assert(file:read(4) == "MLZS", "not a modlib zlib compressed bluon schematic")
return schematic.setmetatable(read_bluon(modlib.text.inputstream(minetest.decompress(file:read"*a", "deflate"))))
end

@ -339,25 +339,25 @@ end
-- Recursively counts occurences of objects (non-primitives including strings) in a table.
function count_objects(value)
local counts = {}
local counts = {}
if value == nil then
-- Early return for nil
return counts
end
local function count_values(value)
local function count_values(value)
local type_ = type(value)
if type_ == "boolean" or type_ == "number" then return end
local count = counts[value]
counts[value] = (count or 0) + 1
if not count and type_ == "table" then
for k, v in pairs(value) do
count_values(k)
count_values(v)
end
end
end
count_values(value)
return counts
local count = counts[value]
counts[value] = (count or 0) + 1
if not count and type_ == "table" then
for k, v in pairs(value) do
count_values(k)
count_values(v)
end
end
end
count_values(value)
return counts
end
function foreach_value(table, func)