Storing Data

From Minetest Developer Wiki
Jump to: navigation, search

Contents

Introduction

Before Minetest 0.4.16, storing data could be done in different ways:


Minetest 0.4.16 is a major step forward in that it provides APIs to store information which formerly had to be done using custom code or by abusing existing APIs such as the inventory to store player attributes. This article will give some pointers as to what can be used for what purpose.

Various Layers for (Meta) Data Storage

The new organization provided by Minetest for storing information is now as follows:

  • Node metadata is to be used to store extra information for nodes in the world. This mechanism is positional and for nodes
  • Inventory is to be used to store inventory. Older mods still abuse this mechanism for other purposes of course but should migrate to different means of storing data that does not belong in the inventory. This mechanism is for keeping track of chest, player and other inventories
  • Mod metadata a.k.a. mod storage can be used to store information specific to a mod, such as mod state, configuration data (especially when this can be set at runtime) or even in order to externalize tables from the Lua code. This mechanism is mod level and should be kept local to the mod
  • Player attributes should be used to store information specific to the player such as custom mod user preferences, non-core engine player state to be persisted (e.g. hunger, XP, mana) and other player specific data you may wish to store. This mechanism is player level and can be used to migrate away from storing player state/ information in inventories


If the above mechanisms do not fit your use case, you can always consider:

  • Custom file management. There is nothing wrong with this of course. It's just that when the engine manages things for you, you do not have to worry about the file management yourself and you can use standardized APIs instead of rolling your own
  • Using a Lua database client or embedded database e.g. FlatDB (https://github.com/uleelx/FlatDB), redis-lua (https://github.com/nrk/redis-lua) or a non pure Lua database binding e.g. Lua SQLite if you feel adventurous

Migrating Away from Inventory Based Metadata Storage

This is probably the most important right now due to "inventory abuse", and as such the first example provided here. Consider hbhunger which was built before 0.4.16 and was forced to store state in the player inventory (so please do not consider this example as a critique). In minetest.register_on_joinplayer it had to do:

local name = player:get_player_name()
local inv = player:get_inventory()
inv:set_size("hunger",1)
hbhunger.hunger[name] = hbhunger.get_hunger_raw(player)


Where hbhunger.get_hunger_raw is called, there is yet more calls to modify the inventory:

local inv = player:get_inventory()
if not inv then return nil end
local hgp = inv:get_stack("hunger", 1):get_count()
if hgp == 0 then
	hgp = 21
	inv:set_stack("hunger", 1, ItemStack({name=":", count=hgp}))
else
	hgp = hgp
end
return hgp-1


And to set the hunger it had set_hunger_raw to modify the inventory with new hunger values:

local inv = player:get_inventory()
local name = player:get_player_name()
local value = hbhunger.hunger[name]
if not inv  or not value then return nil end
if value > 30 then value = 30 end
if value < 0 then value = 0 end
 
inv:set_stack("hunger", 1, ItemStack({name=":", count=value+1}))
 
return true


Obviously the initialization where the inventory has to be prepared for usage can be simplified greatly in the new situation by just calling get_hunger_raw in its minetest.register_on_joinplayer:

hbhunger.hunger[name] = hbhunger.get_hunger_raw(player)


Then get_hunger_raw can do:

local hgp = tonumber(player:get_attribute("hunger"))
if hgp == 0 then
	hgp = 21
	player:set_attribute("hunger", tostring(hgp))
else
	hgp = hgp
end
return hgp-1


And set_hunger_raw can do something like:

local name = player:get_player_name()
local value = hbhunger.hunger[name]
if not value then return nil end
if value > 30 then value = 30 end
if value < 0 then value = 0 end
 
player:set_attribute("hunger", tostring(value + 1))
 
return true


As can be seen, calls to initialize, store and retrieve inventories, item stacks and associated complexities are simplified in more understandable attribute getters and setters.

Migrating Away from Custom Mod Storage

Another example which is more focused on mod settings could be the random_messages mod. This mod comes with a configuration file and one has to copy this file in the world folder. This is a prime example where one could benefit from using a mod storage. The function to read the messages from the world folder is such that:

function random_messages.read_messages()
	local line_number = 1
	local input = io.open(minetest.get_worldpath().."/random_messages","r")
	if not input then
		local output = io.open(minetest.get_worldpath().."/random_messages","w")
		output:write("Blame the server admin! He/She has probably not edited the random messages yet.\n")
		output:write("Tell your dumb admin that this line is in (worldpath)/random_messages \n")
		io.close(output)
		input = io.open(minetest.get_worldpath().."/random_messages","r")
	end
	for line in input:lines() do
		random_messages.messages[line_number] = line
		line_number = line_number + 1
	end
	io.close(input)
end


All I/O can be replaced by a single call to retrieve the mod storage at the top of the init.lua (this is a must!) and then populating the messages table:

local mod_storage = minetest.get_mod_storage()
 
function random_messages.read_messages()
	random_message.messages = mod_storage:to_table() -- Assuming there are only messages in the mod configuration
end


If the file in e.g. worlds/yourworld/mod_storage/random_messages was formatted properly you would already be done loading the random messages. What's even better is that you can now add chat commands to manage the messages table and e.g. store the table via mod storage's from_table when the chat command is run or the server is shutdown. This way you can very easily add messages at runtime without having to restart the server while making sure these messages are persisted across server restarts.

Personal tools
Namespaces

Variants
Actions
Navigation
API
Toolbox