aboutsummaryrefslogtreecommitdiff
path: root/advtrains/nodedb.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains/nodedb.lua')
-rw-r--r--advtrains/nodedb.lua391
1 files changed, 391 insertions, 0 deletions
diff --git a/advtrains/nodedb.lua b/advtrains/nodedb.lua
new file mode 100644
index 0000000..c664792
--- /dev/null
+++ b/advtrains/nodedb.lua
@@ -0,0 +1,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,
+ })
+