Körperbewegung

This commit is contained in:
N-Nachtigal 2025-05-13 23:14:13 +02:00
parent b16b24e4f7
commit 95945c0306
78 changed files with 12503 additions and 0 deletions

79
mods/modlib/doc/b3d.md Normal file
View file

@ -0,0 +1,79 @@
# B3D Reader & Writer
## `b3d.read(file)`
Reads from `file`, which is expected to provide `file:read(nbytes)`. `file` is not closed.
Returns a B3D model object.
## `:write(file)`
Writes the B3D model object `self` to `file`.
`file` must provide `file:write(bytestr)`. It should be in binary mode.
It is not closed after writing.
## `:write_string()`
Writes the B3D model object to a bytestring, which is returned.
## `:to_gltf()`
Returns a glTF JSON representation of `self` in Lua table format.
## `:write_gltf(file)`
Convenience function to write the glTF representation to `file` using modlib's `json` writer.
`file` must provide `file:write(str)`. It is not closed afterwards.
## Examples
### Converting B3D to glTF
This example loops over all files in `dir_path`, converting them to glTFs which are stored in `out_dir_path`.
```lua
local modpath = minetest.get_modpath(minetest.get_current_modname())
local dir_path = modpath .. "/b3d"
local out_dir_path = modpath .. "/gltf"
for _, filename in ipairs(minetest.get_dir_list(dir_path, false --[[only files]])) do
-- First read the B3D
local in_file = assert(io.open(dir_path .. "/" .. filename, "rb"))
local model = assert(b3d.read(in_file))
in_file:close()
-- Then write the glTF
local out_file = io.open(out_dir_path .. "/" .. filename .. ".gltf", "wb")
model:write_gltf(out_file)
out_file:close()
end
```
### [Round-trip (minifying B3Ds)](https://github.com/appgurueu/modlib_test/blob/f11c8e580e90454bc1adaa11a58e0c0217217d90/b3d.lua)
This example from [`modlib_test`](https://github.com/appgurueu/modlib_test) reads, writes, and then reads again,
in order to verify that no data is lost through writing.
Simply re-writing a model using modlib's B3D writer often reduces model sizes,
since for example modlib does not write `0` weights for bones.
Keep in mind to use the `rb` and `wb` modes for I/O operations
to force Windows systems to not perform a line feed normalization.
### [Extracting triangle sets](https://github.com/appgurueu/ghosts/blob/42a9eb9ee81fc6760a0278d23e4c47bc68bb4919/init.lua#L41-L79)
The [Ghosts](https://github.com/appgurueu/ghosts/) mod extracts triangle sets using the B3D module
to then approximate the player shape using particles picked from these triangles.
### [Animating the player](https://github.com/appgurueu/character_anim/blob/c48b282c0b42b32294ec2fddc03aa93141cbd894/init.lua#L213)
[`character_anim`](https://github.com/appgurueu/character_anim/) uses the B3D module to determine the bone overrides required
for animating the player entirely Lua-side using bone overrides.
### [Generating a Go board](https://github.com/appgurueu/go/blob/997ce85260d232a05dd668c32c6854bf34e3d5be/build/generate_models.lua)
This example from the [Go](https://github.com/appgurueu/go) mod generates a Go board
where for each spot on the board there are two pieces (black and white),
both of which can be moved out of the board using a bone.
It demonstrates how to use the writer (and how the table structure roughly looks like).

View file

@ -0,0 +1,260 @@
************************************************************************************
* Blitz3d file format V0.01 *
************************************************************************************
This document and the information contained within is placed in the Public Domain.
Please visit http://www.blitzbasic.co.nz for the latest version of this document.
Please contact marksibly@blitzbasic.co.nz for more information and general inquiries.
************************************************************************************
* Introduction *
************************************************************************************
The Blitz3D file format specifies a format for storing texture, brush and entity descriptions for
use with the Blitz3D programming language.
The rationale behind the creation of this format is to allow for the generation of much richer and
more complex Blitz3D scenes than is possible using established file formats - many of which do not
support key features of Blitz3D, and all of which miss out on at least some features!
A Blitz3D (.b3d) file is split up into a sequence of 'chunks', each of which can contain data
and/or other chunks.
Each chunk is preceded by an eight byte header:
char tag[4] ;4 byte chunk 'tag'
int length ;4 byte chunk length (not including *this* header!)
If a chunk contains both data and other chunks, the data always appears first and is of a fixed
length.
A file parser should ignore unrecognized chunks.
Blitz3D files are stored little endian (intel) style.
Many aspects of the file format are not quite a 'perfect fit' for the way Blitz3D works. This has
been done mainly to keep the file format simple, and to make life easier for the authors of third
party importers/exporters.
************************************************************************************
* Chunk Types *
************************************************************************************
This lists the types of chunks that can appear in a b3d file, and the data they contain.
Color values are always in the range 0 to 1.
string (char[]) values are 'C' style null terminated strings.
Quaternions are used to specify general orientations. The first value is the quaternion 'w' value,
the next 3 are the quaternion 'vector'. A 'null' rotation should be specified as 1,0,0,0.
Anything that is referenced 'by index' always appears EARLIER in the file than anything that
references it.
brush_id references can be -1: no brush.
In the following descriptions, {} is used to signify 'repeating until end of chunk'. Also, a chunk
name enclosed in '[]' signifies the chunk is optional.
Here we go!
BB3D
int version ;file format version: default=1
[TEXS] ;optional textures chunk
[BRUS] ;optional brushes chunk
[NODE] ;optional node chunk
The BB3D chunk appears first in a b3d file, and its length contains the rest of the file.
Version is in major*100+minor format. To check the version, just divide by 100 and compare it with
the major version your software supports, eg:
if file_version/100>my_version/100
RuntimeError "Can't handle this file version!"
EndIf
if file_version Mod 100>my_version Mod 100
;file is a more recent version, but should still be backwardly compatbile with what we can
handle!
EndIf
TEXS
{
char file[] ;texture file name
int flags,blend ;blitz3D TextureFLags and TextureBlend: default=1,2
float x_pos,y_pos ;x and y position of texture: default=0,0
float x_scale,y_scale ;x and y scale of texture: default=1,1
float rotation ;rotation of texture (in radians): default=0
}
The TEXS chunk contains a list of all textures used in the file.
The flags field value can conditional an additional flag value of '65536'. This is used to indicate that the texture uses secondary UV values, ala the TextureCoords command. Yes, I forgot about this one.
BRUS
int n_texs
{
char name[] ;eg "WATER" - just use texture name by default
float red,green,blue,alpha ;Blitz3D Brushcolor and Brushalpha: default=1,1,1,1
float shininess ;Blitz3D BrushShininess: default=0
int blend,fx ;Blitz3D Brushblend and BrushFX: default=1,0
int texture_id[n_texs] ;textures used in brush
}
The BRUS chunk contains a list of all brushes used in the file.
VRTS:
int flags ;1=normal values present, 2=rgba values present
int tex_coord_sets ;texture coords per vertex (eg: 1 for simple U/V) max=8
int tex_coord_set_size ;components per set (eg: 2 for simple U/V) max=4
{
float x,y,z ;always present
float nx,ny,nz ;vertex normal: present if (flags&1)
float red,green,blue,alpha ;vertex color: present if (flags&2)
float tex_coords[tex_coord_sets][tex_coord_set_size] ;tex coords
}
The VRTS chunk contains a list of vertices. The 'flags' value is used to indicate how much extra
data (normal/color) is stored with each vertex, and the tex_coord_sets and tex_coord_set_size
values describe texture coordinate information stored with each vertex.
TRIS:
int brush_id ;brush applied to these TRIs: default=-1
{
int vertex_id[3] ;vertex indices
}
The TRIS chunk contains a list of triangles that all share a common brush.
MESH:
int brush_id ;'master' brush: default=-1
VRTS ;vertices
TRIS[,TRIS...] ;1 or more sets of triangles
The MESH chunk describes a mesh. A mesh only has one VRTS chunk, but potentially many TRIS chunks.
BONE:
{
int vertex_id ;vertex affected by this bone
float weight ;how much the vertex is affected
}
The BONE chunk describes a bone. Weights are applied to the mesh described in the enclosing ANIM -
in 99% of cases, this will simply be the MESH contained in the root NODE chunk.
KEYS:
int flags ;1=position, 2=scale, 4=rotation
{
int frame ;where key occurs
float position[3] ;present if (flags&1)
float scale[3] ;present if (flags&2)
float rotation[4] ;present if (flags&4)
}
The KEYS chunk is a list of animation keys. The 'flags' value describes what kind of animation
info is stored in the chunk - position, scale, rotation, or any combination of.
ANIM:
int flags ;unused: default=0
int frames ;how many frames in anim
float fps ;default=60
The ANIM chunk describes an animation.
NODE:
char name[] ;name of node
float position[3] ;local...
float scale[3] ;coord...
float rotation[4] ;system...
[MESH|BONE] ;what 'kind' of node this is - if unrecognized, just use a Blitz3D
pivot.
[KEYS[,KEYS...]] ;optional animation keys
[NODE[,NODE...]] ;optional child nodes
[ANIM] ;optional animation
The NODE chunk describes a Blitz3D Entity. The scene hierarchy is expressed by the nesting of NODE
chunks.
NODE kinds are currently mutually exclusive - ie: a node can be a MESH, or a BONE, but not both!
However, it can be neither...if no kind is specified, the node is just a 'null' node - in Blitz3D
speak, a pivot.
The presence of an ANIM chunk in a NODE indicates that an animation starts here in the hierarchy.
This allows animations of differing speeds/lengths to be potentially nested.
There are many more 'kind' chunks coming, including camera, light, sprite, plane etc. For now, the
use of a Pivot in cases where the node kind is unknown will allow for backward compatibility.
************************************************************************************
* Examples *
************************************************************************************
A typical b3d file will contain 1 TEXS chunk, 1 BRUS chunk and 1 NODE chunk, like this:
BB3D
1
TEXS
...list of textures...
BRUS
...list of brushes...
NODE
...stuff in the node...
A simple, non-animating, non-textured etc mesh might look like this:
BB3D
1 ;version
NODE
"root_node" ;node name
0,0,0 ;position
1,1,1 ;scale
1,0,0,0 ;rotation
MESH ;the mesh
-1 ;brush: no brush
VRTS ;vertices in the mesh
0 ;no normal/color info in verts
0,0 ;no texture coords in verts
{x,y,z...} ;vertex coordinates
TRIS ;triangles in the mesh
-1 ;no brush for this triangle
{v0,v1,v2...} ;vertices
A more complex 'skinned mesh' might look like this (only chunks shown):
BB3D
TEXS ;texture list
BRUS ;brush list
NODE ;root node
MESH ;mesh - the 'skin'
ANIM ;anim
NODE ;first child of root node - eg: "pelvis"
BONE ;vertex weights for pelvis
KEYS ;anim keys for pelvis
NODE ;first child of pelvis - eg: "left-thigh"
BONE ;bone
KEYS ;anim keys for left-thigh
NODE ;second child of pelvis - eg: "right-thigh"
BONE ;vertex weights for right-thigh
KEYS ;anim keys for right-thigh
...and so on.

132
mods/modlib/doc/bluon.md Normal file
View file

@ -0,0 +1,132 @@
# Bluon
Binary Lua object notation.
## `new(def)`
```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
}
```
## `:is_valid(object)`
Returns whether the given object can be represented by the instance as boolean.
## `:len(object)`
Returns the expected length of the object if serialized by the current instance in bytes.
## `:write(object, stream)`
Writes the object to a stream supporting `:write(text)`. Throws an error if invalid.
## `:read(stream)`
Reads a single bluon object from a stream supporting `:read(count)`. Throws an error if invalid bluon.
Checking whether the stream has been fully consumed by doing `assert(not stream:read(1))` is left up to the user.
## Format
Bluon uses a "tagged union" binary format:
Values are stored as a one-byte tag followed by the contents of the union.
For frequently used "constants", only a tag is used.
`nil` is an exception; since it can't appear in tables, it gets no tag.
If the value to be written by Bluon is `nil`, Bluon simply writes *nothing*.
The following is an enumeration of tag numbers, which are assigned *in this order*.
* `false`: 0
* `true`: 1
* Numbers:
* Constants: 0, nan, +inf, -inf
* Integers: Little endian:
* Unsigned: `U8`, `U16`, `U32`, `U64`
* Negative: `-U8`, `-U16`, `-U32`, `-U64`
* Floats: Little endian `F32`, `F64`
* Strings:
* Constant: `""`
* Length is written as unsigned integer according to the tag: `S8`, `S16`, `S32`, `S64`
* followed by the raw bytes
* Tables:
* Tags: `M0`, `M8`, `M16`, `M32`, `M64` times `L0`, `L8`, `L16`, `L32`, `L64`
* `M` is more significant than `L`: The order of the cartesian product is `M0L0`, `M0L1`, ...
* List and map part count encoded as unsigned integers according to the tag,
list part count comes first
* followed by all values in the list part written as Bluon
* followed by all key-value pairs in the map part written as Bluon
(first the key is written as Bluon, then the value)
* Reference:
* Reference ID as unsigned integer: `R8`, `R16`, `R32`, `R64`
* References a previously encountered table or string by an index:
All tables and strings are numbered in the order they occur in the Bluon
* Reserved tags:
* All tags <= 55 are reserved. This gives 200 free tags.
## Features
* Embeddable: Written in pure Lua
* Storage efficient: No duplication of strings or reference-equal tables
* Flexible: Can serialize circular references and strings containing null
## Simple example
```lua
local object = ...
-- Write to file
local file = io.open(..., "wb")
modlib.bluon:write(object, file)
file:close()
-- Write to text
local rope = modlib.table.rope{}
modlib.bluon:write(object, rope)
text = rope:to_text()
-- Read from text
local inputstream = modlib.text.inputstream"\1"
assert(modlib.bluon:read(object, rope) == true)
```
## Advanced example
```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
}
-- Write to text
local rope = modlib.table.rope{}
custom_bluon:write(userdata(), rope)
assert(rope:to_text() == "\100userdata")
```

View file

@ -0,0 +1,76 @@
# Minetest Wavefront `.obj` file format specification
Minetest Wavefront `.obj` is a subset of [Wavefront `.obj`](http://paulbourke.net/dataformats/obj/).
It is inferred from the [Minetest Irrlicht `.obj` reader](https://github.com/minetest/irrlicht/blob/master/source/Irrlicht/COBJMeshFileLoader.cpp).
`.mtl` files are not supported since Minetest's media loading process ignores them due to the extension.
## Lines / "Commands"
Irrlicht only looks at the first characters needed to tell commands apart (imagine a prefix tree of commands).
Superfluous parameters are ignored.
Numbers are formatted as either:
* Float: An optional minus sign (`-`), one or more decimal digits, followed by the decimal dot (`.`) then again one or more digits
* Integer: An optional minus sign (`-`) followed by one or more decimal digits
Indexing starts at one. Indices are formatted as integers. Negative indices relative to the end of a buffer are supported.
* Comments: `# ...`; unsupported commands are silently ignored as well
* Groups: `g <name>` or `usemtl <name>`
* Subsequent faces belong to a new group / material, no matter the supplied names
* Each group gets their own material (texture); indices are determined by order of appearance
* Empty groups (groups without faces) are ignored
* Vertices (all numbers): `v <x> <y> <z>`, global to the model
* Texture Coordinates (all numbers): `vt <x> <y>`, global to the model
* Normals (all numbers): `vn <x> <y> <z>`, global to the model
* Faces (all vertex/texcoord/normal indices); always local to the current group:
* `f <v1> <v2> <v3>`
* `f <v1>/<t1> <v2>/<t2> ... <vn>/<tn>`
* `f <v1>//<n1> <v2>/<n2> ... <vn>/<nn>`
* `f <v1>/<t1>/<n1> <v2>/<t2>/<n2> ... <vn>/<tn>/<nn>`
## Coordinate system orientation ("handedness")
Vertex & normal X-coordinates are inverted ($x' = -x$);
texture Y-coordinates are inverted as well ($y' = 1 - y$).
## Example
```obj
# A simple 2³ cube centered at the origin; each face receives a separate texture / tile
# no care was taken to ensure "proper" texture orientation
v -1 -1 -1
v -1 -1 1
v -1 1 -1
v -1 1 1
v 1 -1 -1
v 1 -1 1
v 1 1 -1
v 1 1 1
vn -1 0 0
vn 0 -1 0
vn 0 0 -1
vn 1 0 0
vn 0 1 0
vn 0 0 1
vt 0 0
vt 1 0
vt 0 1
vt 1 1
g negative_x
f 1/1/1 3/3/1 2/2/1 4/4/1
g negative_y
f 1/1/2 5/3/2 2/2/2 6/4/2
g negative_z
f 1/1/3 5/3/3 3/2/3 7/4/3
g positive_x
f 5/1/4 7/3/4 2/2/4 8/4/4
g positive_y
f 3/1/5 7/3/5 4/2/5 8/4/5
g positive_z
f 2/1/6 6/3/6 4/2/6 8/4/6
```

9
mods/modlib/doc/json.md Normal file
View file

@ -0,0 +1,9 @@
# JSON
Advantages over `minetest.write_json`/`minetest.parse_json`:
* Twice as fast in most benchmarks (for pre-5.6 at least)
* Uses streams instead of strings
* Customizable
* Useful error messages
* Pure Lua

View file

@ -0,0 +1,31 @@
# Configuration
## Legacy
1. Configuration is loaded from `<worldpath>/config/<modname>.<extension>`, the following extensions are supported and loaded (in the given order), with loaded configurations overriding properties of previous ones:
1. [`json`](https://json.org)
2. [`lua`](https://lua.org)
3. [`luon`](https://github.com/appgurueu/luon), Lua but without the `return`
4. [`conf`](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt)
2. Settings are loaded from `minetest.conf` and override configuration values
## Locations
0. Default configuration: `<modfolder>/conf.lua`
1. World configuration: `config/<modname>.<format>`
2. Mod configuration: `<modfolder>/conf.<format>`
3. Minetest configuration: `minetest.conf`
## Formats
1. [`lua`](https://lua.org)
* Lua, with the environment being the configuration object
* `field = value` works
* Return new configuration object to replace
2. [`luon`](https://github.com/appgurueu/luon)
* Single Lua literal
* Booleans, numbers, strings and tables
3. [`conf`](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt)
* Minetest-like configuration files
4. [`json`](https://json.org)
* Not recommended

View file

@ -0,0 +1,79 @@
# Schematic
A schematic format with support for metadata and baked light data.
## Table Format
The table format uses a table with the following mandatory fields:
* `size`: Size of the schematic in nodes, vector
* `node_names`: List of node names
* `nodes`: List of node indices (into the `node_names` table)
* `param2s`: List of node `param2` values (numbers)
and the following optional fields:
* `light_values`: List of node `param1` (light) values (numbers)
* `metas`: Map from indices in the cuboid to metadata tables as produced by `minetest.get_meta(pos):to_table()`
A "vector" is a table with fields `x`, `y`, `z` for the 3 coordinates.
The `nodes`, `param2s` and `light_values` lists are in the order dictated by `VoxelArea:iterp` (Z-Y-X).
The cuboid indices for the `metas` table are calculated as `(z * size.y) + y * size.x + x` where `x`, `y`, `z` are relative to the min pos of the cuboid.
## Binary Format
The binary format uses modlib's Bluon to write the table format.
Since `param2s` (and optionally `light_values`) are all bytes, they are converted from lists of numbers to (byte)strings before writing.
For uncompressed files, it uses `MLBS` (short for "ModLib Bluon Schematic") for the magic bytes,
followed by the raw Bluon binary data.
For compressed files, it uses `MLZS` (short for "ModLib Zlib-compressed Schematic") for the magic bytes,
followed by the zlib-compressed Bluon binary data.
## API
### `schematic.setmetatable(obj)`
Sets the metatable of a table `obj` to the schematic metatable.
Useful if you've deserialized a schematic or want to create a schematic from the table format.
### `schematic.create(params, pos_min, pos_max)`
Creates a schematic from a map cuboid
* `params`: Table with fields
* `metas` (default `true`): Whether to store metadata
* `light_values`: Whether to bake light values (`param1`).
Usually not recommended, default `false`.
* `pos_min`: Minimum position of the cuboid, inclusive
* `pos_max`: Maximum position of the cuboid, inclusive
### `schematic:place(pos_min)`
"Inverse" to `schematic.create`: Places the schematic `self` starting at `pos_min`.
Content IDs (nodes), param1s, param2s, and metadata in the area will be completely erased and replaced; if light data is present, param1s will simply be set, otherwise they will be recalculated.
### `schematic:write_zlib_bluon(path)`
Write a binary file containing the schematic in *zlib-compressed* binary format to `path`.
**You should generally prefer this over `schematic:write_bluon`: zlib compression comes with massive size reductions.**
### `schematic.read_zlib_bluon(path)`
"Inverse": Read a binary file containing a schematic in *zlib-compressed* binary format from `path`, returning a `schematic` instance.
**You should generally prefer this over `schematic.read_bluon`: zlib compression comes with massive size reductions.**
### `schematic:write_bluon(path)`
Write a binary file containing the schematic in uncompressed binary format to `path`.
Useful only if you want to eliminate the time spent compressing.
### `schematic.read_bluon(path)`
"Inverse": Read a binary file containing a schematic in uncompressed binary format from `path`, returning a `schematic` instance.
Useful only if you want to eliminate the time spent decompressing.

View file

@ -0,0 +1,39 @@
# Texture Modifiers
## Specification
Refer to the following "specifications", in this order of precedence:
1. [Minetest Docs](https://github.com/minetest/minetest_docs/blob/master/doc/texture_modifiers.adoc)
2. [Minetest Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt), section "texture modifiers"
3. [Minetest Sources](https://github.com/minetest/minetest/blob/master/src/client/tile.cpp)
## Implementation
### Constructors ("DSL")
Constructors are kept close to the original forms and perform basic validation. Additionally, texture modifiers can directly be created using `texmod{type = "...", ...}`, bypassing the checks.
### Writing
The naive way to implement string building would be to have a
`tostring` function recursively `tostring`ing the sub-modifiers of the current modifier;
each writer would only need a stream (often passed in the form of a `write` function).
The problem with this is that applying escaping quickly makes this run in quadratic time.
A more efficient approach passes the escaping along with the `write` function. Thus a "writer" object `w` encapsulating this state is passed around.
The writer won't necessarily produce the *shortest* or most readable texture modifier possible; for example, colors will be converted to hexadecimal representation, and texture modifiers with optional parameters may have the default values be written.
You should not rely on the writer to produce any particular of the various valid outputs.
### Reading
**The reader does not attempt to precisely match the behavior of Minetest's shotgun "parser".** It *may* be more strict in some instances, rejecting insane constructs Minetest's parser allows.
It *may* however sometimes also be more lenient (though I haven't encountered an instance of this yet), accepting sane constructs which Minetest's parser rejects due to shortcomings in its implementation.
The parser is written *to spec*, in the given order of precedence.
If a documented construct is not working, that's a bug. If a construct which is incorrect according to the docs is accepted, that's a bug too.
Compatibility with Minetest's parser for all reasonable inputs is greatly valued. If an invalid input is notably used in the wild (or it is reasonable that it may occur in the wild) and supported by Minetest, this parser ought to support it too.
Recursive descent parsing is complicated by the two forms of escaping texture modifiers support: Reading each character needs to handle escaping. The current depth and whether the parser is inside an inventorycube need to be saved in state variables. These could be passed on the stack, but it's more comfortable (and possibly more efficient) to just share them across all functions and restore them after leaving an inventorycube / moving to a lower level.

View file

@ -0,0 +1,23 @@
# Lua Log Files
A data log file based on Lua statements. High performance. Example from `test.lua`:
```lua
local logfile = persistence.lua_log_file.new(mod.get_resource"logfile.test.lua", {})
logfile:init()
logfile.root = {}
logfile:rewrite()
logfile:set_root({a = 1}, {b = 2, c = 3})
logfile:close()
logfile:init()
assert(table.equals(logfile.root, {[{a = 1}] = {b = 2, c = 3}}))
```
Both strings and tables are stored in a reference table. Unused strings won't be garbage collected as Lua doesn't allow marking them as weak references.
This means that setting lots of temporary strings will waste memory until you call `:rewrite()` on the log file. An alternative is to set the third parameter, `reference_strings`, to `false` (default value is `true`):
```lua
persistence.lua_log_file.new(mod.get_resource"logfile.test.lua", {}, false)
```
This will prevent strings from being referenced, possibly bloating file size, but saving memory.

View file

@ -0,0 +1,41 @@
# SQLite3 Database Persistence
Uses a SQLite3 database to persistently store a Lua table. Obtaining it is a bit trickier, as it requires access to the `lsqlite3` library, which may be passed:
```lua
local modlib_sqlite3 = persistence.sqlite3(require"lsqlite3")
```
(assuming `require` is that of an insecure environment if Minetest is used)
Alternatively, if you are not running Minetest, mod security is disabled, you have (temporarily) provided `require` globally, or added `modlib` to `secure.trusted_mods`, you can simply do the following:
```lua
local modlib_sqlite3 = persistence.sqlite3()
```
Modlib will then simply call `require"lsqlite3"` for you.
Then, you can proceed to create a new database:
```lua
local database = persistence.modlib_sqlite3.new(mod.get_resource"database.test.sqlite3", {})
-- Create or load
database:init()
-- Use it
database:set_root("key", {nested = true})
database:close()
```
It uses a similar API to Lua log files:
* `new(filename, root)` - without `reference_strings` however (strings aren't referenced currently)
* `init`
* `set`
* `set_root`
* `rewrite`
* `close`
The advantage over Lua log files is that the SQlite3 database keeps disk usage minimal. Unused tables are dropped from the database immediately through reference counting. The downside of this is that this, combined with the overhead of using SQLite3, of course takes time, making updates on the SQLite3 database slower than Lua log file updates (which just append to an append-only file).
As simple and fast reference counting doesn't handle cycles, an additional `collectgarbage` stop-the-world method performing a full garbage collection on the database is provided which is called during `init`.
The method `defragment_ids` should not have to be used in practice (if it has to be, it happens automatically) and should be used solely for debugging purposes (neater IDs).

47
mods/modlib/doc/schema.md Normal file
View file

@ -0,0 +1,47 @@
# Schema
Place a file `schema.lua` in your mod, returning a schema table.
## Non-string entries and `minetest.conf`
Suppose you have the following schema:
```lua
return {
type = "table",
entries = {
[42] = {
type = "boolean",
description = "The Answer"
default = true
}
}
}
```
And a user sets the following config:
```conf
mod.42 = false
```
It won't work, as the resulting table will be `{["42"] = false}` instead of `{[42] = false}`. In order to make this work, you have to convert the keys yourself:
```lua
return {
type = "table",
keys = {
-- this will convert all keys to numbers
type = "number"
},
entries = {
[42] = {
type = "boolean",
description = "The Answer"
default = true
}
}
}
```
This is best left explicit. First, you shouldn't be using numbered field keys if you want decent `minetest.conf` support, and second, `modlib`'s schema module could only guess in this case, attempting conversion to number / boolean. What if both number and string field were set as possible entries? Should the string field be deleted? And so on.