aboutsummaryrefslogtreecommitdiff
path: root/src/content_nodemeta.cpp
Commit message (Expand)AuthorAge
* C++11 patchset 9: move hardcoded init parameters to class definitions (part 1...Loïc Blot2017-06-16
* Make node timers more efficientEkdohibs2016-06-11
* Rename macros with two leading underscoresShadowNinja2015-10-14
* Use warningstream for log messages with WARNINGShadowNinja2015-10-14
* Various style cleanups + unused code removalest312015-09-19
* Omnicleanup: header cleanup, add ModApiUtil shared between game and mainmenuKahrl2013-08-14
* Update Copyright YearsSfan52013-02-24
* Change Minetest-c55 to MinetestPilzAdam2013-02-24
* Implement formspecdarkrose2012-07-22
* Properly and efficiently use split utility headersPerttu Ahola2012-06-17
* Switch the license to be LGPLv2/later, with small parts still remaining as GP...Perttu Ahola2012-06-05
* Fix loading of old signsPerttu Ahola2012-06-04
* Fix loading of legacy chestsPerttu Ahola2012-06-03
* Implement sign using form field protocolPerttu Ahola2012-06-03
* Convert legacy chest inventory list "0" to "main"Perttu Ahola2012-06-03
* Strip unneeded variables from NodeMetadatadarkrose2012-06-03
* WIP node metadata, node timersKahrl2012-06-03
* Fix server hang when stepping a furnace with a huge dtimeKahrl2012-02-02
* The huge item definition and item namespace unification patch (itemdef), see ...Kahrl2012-01-12
* Move NodeMetadata prototype containers to content_nodemeta.cpp to fix them no...Perttu Ahola2011-12-01
* GenericNodeMetadata and an example furnacePerttu Ahola2011-11-29
* Mode node definition loading from Lua (still not finished), fix metadata crea...Perttu Ahola2011-11-29
* Add names to NodeMetadataPerttu Ahola2011-11-29
* Do not expose CONTENT_* stuff in content_mapnode.h and use a name converter w...Perttu Ahola2011-11-29
* Remove burn time definitions from FurnaceNodeMetadataPerttu Ahola2011-11-29
* Clean mapnode.h and fix other files accordinglyPerttu Ahola2011-11-29
* Create framework for getting rid of global definitions of node/tool/item/what...Perttu Ahola2011-11-29
* Switch more stuff to use the logging thing and fix segfault on player leave f...Perttu Ahola2011-10-16
* added locking chests - clean patchdarkrose2011-09-22
* Furnace was crashing stuff againJacobF2011-09-02
* * Furnace would say it was overloaded when it had a non-cookable sourceJacobF2011-09-01
* If there was no source item in a furnace it would cause a segmentation fault.JacobF2011-08-30
* Merge remote-tracking branch 'queatz/furnace-is-not-blackhole'Perttu Ahola2011-08-30
|\
| * Don't remove furnace if something is inside it.JacobF2011-08-24
* | Merged 2 branches because they relied on each other.JacobF2011-08-25
|/
* Some inventory const-ificationGiuseppe Bilotta2011-08-11
* made more things burnable in furnacePerttu Ahola2011-07-30
* fixed wrong furnace menu layoutPerttu Ahola2011-06-18
* moved inventory menu definition of chest and furnace to content_nodemeta.{h,cpp}Perttu Ahola2011-06-18
* Created and moved stuff to content_nodemeta.{h,cpp}Perttu Ahola2011-06-18
/a> 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
--nodedb.lua
--database of all nodes that have 'save_in_at_nodedb' field set to true in node definition


--serialization format:
--(2byte z) (2byte y) (2byte x) (2byte contentid)
--contentid := (14bit nodeid, 2bit param2)

local function int_to_bytes(i)
	local x=i+32768--clip to positive integers
	local cH = math.floor(x /           256) % 256;
	local cL = math.floor(x                ) % 256;
	return(string.char(cH, cL));
end
local function bytes_to_int(bytes)
	local t={string.byte(bytes,1,-1)}
	local n = 
		t[1] *           256 +
		t[2]
    return n-32768
end
local function l2b(x)
	return x%4
end
local function u14b(x)
	return math.floor(x/4)
end
local ndb={}

--local variables for performance
local ndb_nodeids={}
local ndb_nodes={}
local ndb_ver

local function ndbget(x,y,z)
	local ny=ndb_nodes[y]
	if ny then
		local nx=ny[x]
		if nx then
			return nx[z]
		end
	end
	return nil
end
local function ndbset(x,y,z,v)
	if not ndb_nodes[y] then
		ndb_nodes[y]={}
	end
	if not ndb_nodes[y][x] then
		ndb_nodes[y][x]={}
	end
	ndb_nodes[y][x][z]=v
end

-- load/save

local path_pre_v4=minetest.get_worldpath()..DIR_DELIM.."advtrains_ndb2"
--load pre_v4 format
--nodeids get loaded by advtrains init.lua and passed here
function ndb.load_data_pre_v4(data)
	atlog("nodedb: Loading pre v4 format")

	ndb_nodeids = data and data.nodeids or {}
	ndb_ver = data and data.ver or 0
	if ndb_ver < 1 then
		for k,v in pairs(ndb_nodeids) do
			if v == "advtrains:dtrack_xing4590_st" then
				cidDepr = k
			elseif v == "advtrains:dtrack_xing90plusx_45l" then
				cidNew = k
			end
		end
	end
	local file, err = io.open(path_pre_v4, "rb")
	if not file then
		atwarn("Couldn't load the node database: ", err or "Unknown Error")
	else
		-- Note: code duplication because of weird coordinate order in ndb2 format (z,y,x)
		local cnt=0
		local hst_z=file:read(2)
		local hst_y=file:read(2)
		local hst_x=file:read(2)
		local cid=file:read(2)
		while hst_z and hst_y and hst_x and cid and #hst_z==2 and #hst_y==2 and #hst_x==2 and #cid==2 do
			if (ndb_ver < 1 and cid == cidDepr) then
				cid = cidNew
			end
			ndbset(bytes_to_int(hst_x), bytes_to_int(hst_y), bytes_to_int(hst_z), bytes_to_int(cid))
			cnt=cnt+1
			hst_z=file:read(2)
			hst_y=file:read(2)
			hst_x=file:read(2)
			cid=file:read(2)
		end
		atlog("nodedb (ndb2 format): read", cnt, "nodes.")
		file:close()
	end
	ndb_ver = 1
end

-- the new ndb file format is backported from cellworld, and stores the cids also in the ndb file.
-- These functions have the form of a serialize_lib atomic load/save callback and are called from avt_save/avt_load.
function ndb.load_callback(file)
	-- read version
	local vers_byte = file:read(1)
	local version = string.byte(vers_byte)
	if version~=1 then
		file:close()
		error("Doesn't support v4 nodedb file of version "..version)
	end
	
	-- read cid mappings
	local nstr_byte = file:read(2)
	local nstr = bytes_to_int(nstr_byte)
	for i = 1,nstr do
		local stid_byte = file:read(2)
		local stid = bytes_to_int(stid_byte)
		local stna = file:read("*l")
		--atdebug("content id:", stid, "->", stna)
		ndb_nodeids[stid] = stna
	end
	atlog("[nodedb] read", nstr, "node content ids.")

	-- read nodes
	local cnt=0
	local hst_x=file:read(2)
	local hst_y=file:read(2)
	local hst_z=file:read(2)
	local cid=file:read(2)
	local cidi
	while hst_z and hst_y and hst_x and cid and #hst_z==2 and #hst_y==2 and #hst_x==2 and #cid==2 do
		cidi = bytes_to_int(cid)
		-- prevent file corruption already here
		if not ndb_nodeids[u14b(cidi)] then
			-- clear the ndb data, to reinitialize it
			-- in strict loading mode, doesn't matter as starting will be interrupted anyway
			ndb_nodeids = {}
			ndb_nodes = {}
			error("NDB file is corrupted (found entry with invalid cid)")
		end
		ndbset(bytes_to_int(hst_x), bytes_to_int(hst_y), bytes_to_int(hst_z), cidi)
		cnt=cnt+1
		hst_x=file:read(2)
		hst_y=file:read(2)
		hst_z=file:read(2)
		cid=file:read(2)
	end
	atlog("[nodedb] read", cnt, "nodes.")
	file:close()
end

--save
function ndb.save_callback(data, file)
	--atdebug("storing ndb...")
	-- write version
	file:write(string.char(1))
	
	-- how many cid entries
	local cnt = 0
	for _,_ in pairs(ndb_nodeids) do
		cnt = cnt + 1
	end
	-- write cids
	local nstr = 0
	file:write(int_to_bytes(cnt))
	for stid,stna in pairs(ndb_nodeids) do
		file:write(int_to_bytes(stid))
		file:write(stna)
		file:write("\n")
		nstr = nstr+1
	end
	--atdebug("saved cids count ", nstr)
	
	-- write entries
	local cnt = 0
	for y, ny in pairs(ndb_nodes) do
		for x, nx in pairs(ny) do
			for z, cid in pairs(nx) do
				file:write(int_to_bytes(x))
				file:write(int_to_bytes(y))
				file:write(int_to_bytes(z))
				file:write(int_to_bytes(cid))
				cnt=cnt+1
			end
		end
	end
	--atdebug("saved nodes count ", cnt)
	file:close()
end



--function to get node. track database is not helpful here.
function ndb.get_node_or_nil(pos)
	-- FIX for bug found on linuxworks server:
	-- a loaded node might get read before the LBM has updated its state, resulting in wrongly set signals and switches
	-- -> Using the saved node prioritarily.
	local node = ndb.get_node_raw(pos)
	if node then
		return node
	else
		--try reading the node from the map
		return minetest.get_node_or_nil(pos)
	end
end
function ndb.get_node(pos)
	local n=ndb.get_node_or_nil(pos)
	if not n then
		return {name="ignore", param2=0}
	end
	return n
end
function ndb.get_node_raw(pos)
	local cid=ndbget(pos.x, pos.y, pos.z)
	if cid then
		local nodeid = ndb_nodeids[u14b(cid)]
		if nodeid then
			return {name=nodeid, param2 = l2b(cid)}
		end
	end
	return nil
end


function ndb.swap_node(pos, node, no_inval)
	if advtrains.is_node_loaded(pos) then
		minetest.swap_node(pos, node)
	end
	ndb.update(pos, node)
end

function ndb.update(pos, pnode)
	local node = pnode or minetest.get_node_or_nil(pos)
	if not node or node.name=="ignore" then return end
	if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].groups.save_in_at_nodedb then
		local nid
		for tnid, nname in pairs(ndb_nodeids) do
			if nname==node.name then
				nid=tnid
			end
		end
		if not nid then
			nid=#ndb_nodeids+1
			ndb_nodeids[nid]=node.name
		end
		local resid = (nid * 4) + (l2b(node.param2 or 0))
		ndbset(pos.x, pos.y, pos.z, resid )
		--atdebug("nodedb: updating node", pos, "stored nid",nid,"assigned",ndb_nodeids[nid],"resulting cid",resid)
		advtrains.invalidate_all_paths_ahead(pos)
	else
		--at this position there is no longer a node that needs to be tracked.
		--atdebug("nodedb: updating node", pos, "cleared")
		ndbset(pos.x, pos.y, pos.z, nil)
	end
end

function ndb.clear(pos)
	ndbset(pos.x, pos.y, pos.z, nil)
end


--get_node with pseudoload. now we only need track data, so we can use the trackdb as second fallback
--nothing new will be saved inside the trackdb.
--returns:
--true, conn1, conn2, rely1, rely2, railheight   in case everything's right.
--false  if it's not a rail or the train does not drive on this rail, but it is loaded or
--nil    if the node is neither loaded nor in trackdb
--the distraction between false and nil will be needed only in special cases.(train initpos)
function advtrains.get_rail_info_at(pos, drives_on)
	local rdp=advtrains.round_vector_floor_y(pos)
	
	local node=ndb.get_node_or_nil(rdp)
	if not node then return end
	
	local nodename=node.name
	if(not advtrains.is_track_and_drives_on(nodename, drives_on)) then
		return false
	end
	local conns, railheight, tracktype=advtrains.get_track_connections(node.name, node.param2)
	
	return true, conns, railheight
end

local IGNORE_WORLD = advtrains.IGNORE_WORLD

ndb.run_lbm = function(pos, node)
		local cid=ndbget(pos.x, pos.y, pos.z)
		if cid then
			--if in database, detect changes and apply.
			local nodeid = ndb_nodeids[u14b(cid)]
			local param2 = l2b(cid)
			if not nodeid then
				--something went wrong
				atwarn("Node Database corruption, couldn't determine node to set at", pos)
				ndb.update(pos, node)
			else
				if (nodeid~=node.name or param2~=node.param2) then
					--atprint("nodedb: lbm replaced", pos, "with nodeid", nodeid, "param2", param2, "cid is", cid)
					local newnode = {name=nodeid, param2 = param2}
					minetest.swap_node(pos, newnode)
					local ndef=minetest.registered_nodes[nodeid]
					if ndef and ndef.advtrains and ndef.advtrains.on_updated_from_nodedb then
						ndef.advtrains.on_updated_from_nodedb(pos, newnode)
					end
					return true
				end
			end
		else
			--if not in database, take it.
			--atlog("Node Database:", pos, "was not found in the database, have you used worldedit?")
			ndb.update(pos, node)
		end
		return false
end


minetest.register_lbm({
        name = "advtrains:nodedb_on_load_update",
        nodenames = {"group:save_in_at_nodedb"},
        run_at_every_load = true,
        run_on_every_load = true,
        action = ndb.run_lbm,
        interval=30,
        chance=1,
    })

--used when restoring stuff after a crash
ndb.restore_all = function()
	--atlog("Updating the map from the nodedb, this may take a while")
	local t1 = os.clock()
	local cnt=0
	local dcnt=0
	for y, ny in pairs(ndb_nodes) do
		for x, nx in pairs(ny) do
			for z, _ in pairs(nx) do
				local pos={x=x, y=y, z=z}
				local node=minetest.get_node_or_nil(pos)
				if node then
					local ori_ndef=minetest.registered_nodes[node.name]
					local ndbnode=ndb.get_node_raw(pos)
					if (ori_ndef and ori_ndef.groups.save_in_at_nodedb) or IGNORE_WORLD then --check if this node has been worldedited, and don't replace then
						if (ndbnode.name~=node.name or ndbnode.param2~=node.param2) then
							minetest.swap_node(pos, ndbnode)
							--atlog("Replaced",node.name,"@",pos,"with",ndbnode.name)
							cnt=cnt+1
						end
					else
						ndb.clear(pos)
						dcnt=dcnt+1
						--atlog("Found ghost node (former",ndbnode and ndbnode.name,") @",pos,"deleting")
					end
				end
			end
		end
	end
	local text="Restore node database: Replaced "..cnt.." nodes, removed "..dcnt.." ghost nodes. (took "..math.floor((os.clock()-t1) * 1000).."ms)"
	atlog(text)
	return text
end
    
minetest.register_on_dignode(function(pos, oldnode, digger)
		ndb.clear(pos)
end)

function ndb.get_nodes()
	return ndb_nodes
end
function ndb.get_nodeids()
	return ndb_nodeids
end


advtrains.ndb=ndb

local ptime=0

minetest.register_chatcommand("at_sync_ndb",
	{
        params = "", -- Short parameter description
        description = "Write node db back to map and find ghost nodes", -- Full description
        privs = {train_operator=true}, 
        func = function(name, param)
				if os.time() < ptime+30 and not minetest.get_player_privs(name, "server") then
					return false, "Please wait at least 30s from the previous execution of /at_restore_ndb!"
				end
				local text = ndb.restore_all()
				ptime=os.time()
				return true, text
        end,
    })