nodestore.lua 10.0 KB

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