wot/dbchunkpool.py
Joscha aca387cf05 Save chunks in db
One of the changes introduced for this is that multiple chunks and
diffs are now represented by dicts, not lists.
2017-04-16 17:02:32 +00:00

170 lines
3.8 KiB
Python

import sqlite3
import time
import threading
from chunks import ChunkPool, Chunk
from utils import Position, CHUNK_WIDTH, CHUNK_HEIGHT
class ChunkDB():
"""
Load and save chunks to a SQLite db.
"""
def __init__(self, filename):
self.dbfilename = filename
self._create_table()
def transaction(func):
def wrapper(self, *args, **kwargs):
con = sqlite3.connect(self.dbfilename)
try:
with con:
return func(self, con, *args, **kwargs)
finally:
con.close()
return wrapper
@transaction
def _create_table(self, con):
cur = con.cursor()
cur.execute(("CREATE TABLE IF NOT EXISTS chunks ("
"x INTEGER NOT NULL, "
"y INTEGER NOT NULL, "
"content TEXT, "
"PRIMARY KEY (x, y)"
")"))
@transaction
def save_many(self, con, chunks):
lchunks = ChunkDB.chunks_to_list(chunks)
con.executemany("INSERT OR REPLACE INTO chunks VALUES (?, ?, ?)", lchunks)
@transaction
def load_many(self, con, coords):
cur = con.cursor()
results = []
for pos in coords:
cur.execute("SELECT * FROM chunks WHERE x=? AND y=?", pos)
results.extend(cur.fetchall())
results = ChunkDB.list_to_chunks(results)
return results
@transaction
def remove_empty(self, con):
con.execute("DELETE FROM chunks WHERE content=?", (" "*CHUNK_WIDTH*CHUNK_HEIGHT,))
@staticmethod
def list_to_chunks(l):
chunks = {}
for item in l:
pos = Position(item[0], item[1])
chunk = Chunk.from_string(item[2])
chunks[pos] = chunk
return chunks
@staticmethod
def chunks_to_list(chunks):
l = []
for pos, chunk in chunks.items():
l.append((pos[0], pos[1], chunk.to_string()))
return l
class DBChunkPool(ChunkPool):
"""
A ChunkPool that can load/save chunks from/to a database.
"""
def __init__(self, filename):
super().__init__()
self._chunkdb = ChunkDB(filename)
self.save_period = 60 # save and clean up every minute
self.max_age = 60 # ca. one minute until a chunk is unloaded again
self.save_thread = threading.Thread(
target=self.perodic_save,
name="save_thread",
daemon=True
)
self.save_thread.start()
def save_changes(self):
diffs = self.commit_changes()
changed_chunks = {}
for pos, diff in diffs.items():
chunk = self.get(pos)
changed_chunks[pos] = chunk
self._chunkdb.save_many(changed_chunks)
def load(self, pos):
raise Exception
def load_list(self, coords):
to_load = [pos for pos in coords if pos not in self._chunks]
chunks = self._chunkdb.load_many(to_load)
for pos in to_load:
if pos in chunks:
self.set(pos, chunks.get(pos))
else:
self.create(pos)
def perodic_save(self):
while True:
time.sleep(self.save_period)
with self:
self.save_changes()
# unload old chunks
now = time.time()
self.clean_up(condition=lambda pos, chunk: chunk.age(now) > self.max_age)
def remove_empty(self):
self._chunkdb.remove_empty()
def _get_min_max(self):
"""
Meant for debugging.
"""
minx = min(pos.x for pos in self._chunks)
maxx = max(pos.x for pos in self._chunks)
miny = min(pos.y for pos in self._chunks)
maxy = max(pos.y for pos in self._chunks)
return minx, maxx, miny, maxy
def _print_chunks(self):
"""
Meant for debugging.
"""
if self._chunks:
minx, maxx, miny, maxy = self._get_min_max()
sizex, sizey = maxx - minx + 1, maxy - miny + 1
print("" + ""*sizex*2 + "")
for y in range(miny, maxy + 1):
line = []
for x in range(minx, maxx + 1):
chunk = self._chunks.get(Position(x, y))
if chunk:
if chunk.empty():
line.append("()")
else:
line.append("[]")
else:
line.append(" ")
line = "".join(line)
print("" + line + "")
print("" + ""*sizex*2 + "")