nodestore.lua 10 KB

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