nodestore.lua 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. nodestore = nodestore or {}
  2. nodestore.modpath = minetest.get_modpath("networks")
  3. nodestore.worldpath = minetest.get_worldpath()
  4. nodestore.database = nodestore.worldpath .. "/nodestore.sqlite"
  5. -- Nodes in the nodestore are indexed by block sector, then hash.
  6. nodestore.data = nodestore.data or {} -- Nodes, indexed by sector.
  7. nodestore.dirty = nodestore.dirty or {} -- List of dirty sectors.
  8. -- Cache for speed.
  9. local myfloor = math.floor
  10. local myhash = minetest.hash_node_position -- TODO: could I implement this in Lua for speed?
  11. -- Private function!
  12. -- Create db:exec wrapper for error reporting.
  13. function nodestore.db_exec(stmt)
  14. if nodestore.db:exec(stmt) ~= nodestore.sql.OK then
  15. local msg = nodestore.db:errmsg()
  16. minetest.log("error", "Sqlite ERROR: " .. msg)
  17. if minetest.get_player_by_name("MustTest") then
  18. minetest.chat_send_player("MustTest", "# Server: Error from SQL! " .. msg)
  19. end
  20. end
  21. end
  22. -- Private function!
  23. local function pos_to_sector(pos)
  24. local sx = myfloor(pos.x/16)
  25. local sy = myfloor(pos.y/16)
  26. local sz = myfloor(pos.z/16)
  27. return myhash({x=sx, y=sy, z=sz})
  28. -- Number as returned is not suitable for use in filename, must transform first!
  29. end
  30. -- Private function!
  31. local function table_not_empty(tb)
  32. local c = 0
  33. for k, v in pairs(tb) do
  34. c = 1
  35. break
  36. end
  37. return (c > 0)
  38. end
  39. -- Private function!
  40. local function sector_to_keyname(sector)
  41. local keyname = minetest.serialize(sector)
  42. assert(type(keyname) == "string")
  43. keyname = string.gsub(keyname, "return ", "")
  44. assert(string.len(keyname) > 0)
  45. return keyname
  46. end
  47. -- Private function!
  48. function nodestore.log(msg)
  49. minetest.log("action", "[nodestore]: " .. msg)
  50. end
  51. -- Public API function.
  52. --
  53. -- Shall return the declared name and declared owner of a node at a position,
  54. -- reading the data from memory if possible, otherwise reading the map directly.
  55. -- If the map is read from, the data is cached in anticipation of future reads.
  56. function nodestore.get_nodename_and_realowner(pos, hash, netowner)
  57. local sector = pos_to_sector(pos)
  58. nodestore.do_load(sector)
  59. do
  60. local node = nodestore.data[sector][hash]
  61. if node then
  62. -- Note: owner will not necessarily match netowner.
  63. return node.name, node.owner
  64. end
  65. end
  66. -- Otherwise, we have to read from the map.
  67. local meta = minetest.get_meta(pos)
  68. local realname = meta:get_string("nodename")
  69. local realowner = meta:get_string("owner")
  70. nodestore.data[sector][hash] = {
  71. name = realname,
  72. owner = realowner,
  73. }
  74. nodestore.dirty[sector] = true
  75. return realname, realowner
  76. end
  77. -- Public API function.
  78. --
  79. -- Shall read the node at the passed position, and store in the database its
  80. -- declared owner and declared name. The database is declared 'dirty'.
  81. -- The node must declare its owner and name in the nodemeta, using 'owner' and
  82. -- 'nodename' keys. Note that 'nodename' should be set on construction and not
  83. -- changed, even if the node is swapped for another version of itself, such as
  84. -- swapping between the active/inactive versions of a default furnace.
  85. function nodestore.add_node(pos)
  86. local meta = minetest.get_meta(pos)
  87. local owner = meta:get_string("owner")
  88. local sector = pos_to_sector(pos)
  89. nodestore.do_load(sector)
  90. local hash = myhash(pos)
  91. nodestore.data[sector][hash] = {
  92. name = meta:get_string("nodename"),
  93. owner = owner,
  94. }
  95. nodestore.dirty[sector] = true
  96. end
  97. -- Public API function.
  98. --
  99. -- Shall delete the node at the given position from the database (if data
  100. -- exists). The database is declared 'dirty' only if data was removed.
  101. function nodestore.del_node(pos)
  102. local sector = pos_to_sector(pos)
  103. nodestore.do_load(sector)
  104. local hash = myhash(pos)
  105. nodestore.data[sector][hash] = nil
  106. nodestore.dirty[sector] = true
  107. end
  108. -- Public API function.
  109. --
  110. -- Shall obtain the hub info for a node at a given position, if the node in that
  111. -- position is a hub node. Will read the data from memory if possible, otherwise
  112. -- the data will be read from the map and then cached in anticipation of future
  113. -- reads.
  114. --
  115. -- Note that this is similar to 'nodestore.get_nodename_and_realowner', but it
  116. -- returns hub data instead of name and owner.
  117. function nodestore.get_hub_info(pos)
  118. local sector = pos_to_sector(pos)
  119. nodestore.do_load(sector)
  120. local hash = myhash(pos)
  121. local node = nodestore.data[sector][hash]
  122. if node and node.hub then
  123. return node.hub
  124. end
  125. -- Otherwise, we have to read the map.
  126. return nodestore.update_hub_info(pos)
  127. end
  128. local keytab = {
  129. -- First: position in a direction [p=position]. Second: is direction enabled
  130. -- [e=enabled]. A search for a valid position in a direction is only done if
  131. -- that position is enabled.
  132. {mp="np", me="ne"},
  133. {mp="sp", me="se"},
  134. {mp="ep", me="ee"},
  135. {mp="wp", me="we"},
  136. {mp="up", me="ue"},
  137. {mp="dp", me="de"},
  138. }
  139. -- Public API function.
  140. --
  141. -- Shall read the hub info, owner, and nodename for a hub node at a given
  142. -- position from the map and store it in the cache. Will also return the hub
  143. -- info that was read (which may be ignored). Note that just as in
  144. -- 'nodestore.add_node', the owner and nodename must be declared with 'owner'
  145. -- and 'nodename' keys in the node metadata. Hub information should be declared
  146. -- with the keys as defined by the 'keytab' table above.
  147. function nodestore.update_hub_info(pos)
  148. local meta = minetest.get_meta(pos)
  149. local data = {}
  150. for k, v in ipairs(keytab) do
  151. local e = meta:get_int(v.me)
  152. data[v.me] = e
  153. if e == 1 then
  154. local ps = meta:get_string(v.mp)
  155. local p = minetest.string_to_pos(ps)
  156. if p then
  157. data[v.mp] = p
  158. else
  159. data[v.mp] = nil
  160. end
  161. else
  162. data[v.mp] = nil
  163. end
  164. end
  165. local sector = pos_to_sector(pos)
  166. nodestore.do_load(sector)
  167. local hash = myhash(pos)
  168. nodestore.data[sector][hash] = {
  169. name = meta:get_string("nodename"),
  170. owner = meta:get_string("owner"),
  171. hub = data,
  172. }
  173. nodestore.dirty[sector] = true
  174. return data
  175. end
  176. -- Private function!
  177. --
  178. -- Write all sectors marked as dirty to the database.
  179. function nodestore.do_save()
  180. -- First check if anything is dirty. Count dirty entries.
  181. local have_dirty = false
  182. for k, v in pairs(nodestore.dirty) do
  183. have_dirty = true
  184. break
  185. end
  186. if have_dirty then
  187. nodestore.db_exec("BEGIN TRANSACTION;")
  188. for sector, v in pairs(nodestore.dirty) do
  189. -- Generate filename.
  190. local keyname = sector_to_keyname(sector)
  191. nodestore.log("Saving sector " .. keyname .. " because it is dirty.")
  192. local data = nodestore.data[sector]
  193. if data and table_not_empty(data) then
  194. local str = minetest.serialize(data)
  195. if type(str) == "string" then
  196. nodestore.db_save_sector(sector, str)
  197. else
  198. nodestore.log("Could not serialize sector " .. keyname .. " to string!")
  199. end
  200. else
  201. nodestore.log("Sector " .. keyname .. " declared dirty, but does not exist in memory or is empty.")
  202. end
  203. end
  204. -- Clear dirty names.
  205. nodestore.dirty = {}
  206. nodestore.db_exec("COMMIT;")
  207. end
  208. end
  209. -- Private function!
  210. --
  211. -- Try to load 'sector' from the database. If the sector exists, load it and
  212. -- populate the in-memory cache. If the sector does not exist in the database,
  213. -- create an empty in-memory cache.
  214. function nodestore.do_load(sector)
  215. -- If data is already loaded, then do nothing.
  216. if nodestore.data[sector] then
  217. return
  218. end
  219. -- Generate sector name for use in messages.
  220. local keyname = sector_to_keyname(sector)
  221. -- Attempt to get data from database.
  222. local str = nodestore.db_load_sector(sector)
  223. if str and type(str) == "string" then
  224. local data = minetest.deserialize(str)
  225. if type(data) == "table" then
  226. nodestore.data[sector] = data
  227. nodestore.dirty[sector] = nil
  228. else
  229. nodestore.log("Sector " .. keyname .. " could not be loaded from database, it is corrupt!")
  230. end
  231. else
  232. nodestore.log("Sector " .. keyname .. " does not exist in database. Creating it.")
  233. end
  234. -- Create table if not loaded from database.
  235. if not nodestore.data[sector] then
  236. nodestore.data[sector] = {}
  237. end
  238. end
  239. -- Private function!
  240. function nodestore.db_save_sector(key, data)
  241. local stmt = nodestore.db:prepare([[ INSERT OR REPLACE INTO store (name, data) VALUES (?, ?); ]])
  242. local r1 = stmt:bind(1, key)
  243. assert(r1 == nodestore.sql.OK)
  244. local r2 = stmt:bind_blob(2, data)
  245. assert(r2 == nodestore.sql.OK)
  246. local r3 = stmt:step()
  247. assert(r3 == nodestore.sql.DONE)
  248. local r4 = stmt:finalize()
  249. assert(r4 == nodestore.sql.OK)
  250. end
  251. -- Private function!
  252. function nodestore.db_load_sector(key)
  253. local stmt = nodestore.db:prepare([[ SELECT name, data FROM store WHERE name = ? LIMIT 1; ]])
  254. stmt:bind(1, key)
  255. local r = stmt:step()
  256. while r == nodestore.sql.ROW do
  257. r = stmt:step()
  258. end
  259. assert(r == nodestore.sql.DONE)
  260. for row in stmt:nrows() do
  261. stmt:finalize()
  262. assert(row.data and row.name)
  263. assert(type(row.data) == "string")
  264. assert(row.name == key)
  265. nodestore.log( ("Loaded sector %s from database."):format(sector_to_keyname(key)) )
  266. return row.data
  267. end
  268. -- Returns 'nil' if no record in database.
  269. end
  270. -- Private function!
  271. -- This is needed to ensure dirty sectors are saved before the database is closed.
  272. function nodestore.on_shutdown()
  273. nodestore.do_save()
  274. nodestore.db:close()
  275. end
  276. -- Private function!
  277. function nodestore.create_table()
  278. local stmt = [[
  279. CREATE TABLE IF NOT EXISTS store (name INTEGER PRIMARY KEY, data BLOB) WITHOUT ROWID;
  280. ]]
  281. nodestore.db_exec(stmt)
  282. end
  283. -- One-time execution goes here.
  284. if not nodestore.run_once then
  285. -- Obtain library for database access.
  286. nodestore.sql = require("lsqlite3")
  287. assert(nodestore.sql)
  288. -- Don't allow other mods to use this global library!
  289. if sqlite3 then sqlite3 = nil end
  290. -- Open database.
  291. nodestore.db = nodestore.sql.open(nodestore.database)
  292. assert(nodestore.db)
  293. -- Create table if necessary.
  294. nodestore.create_table()
  295. -- Database save callbacks.
  296. minetest.register_on_mapsave(function(...)
  297. return nodestore.do_save(...) end)
  298. minetest.register_on_shutdown(function(...)
  299. return nodestore.on_shutdown(...) end)
  300. local c = "nodestore:core"
  301. local f = nodestore.modpath .. "/nodestore.lua"
  302. reload.register_file(c, f, false)
  303. nodestore.run_once = true
  304. end