This commit includes a a few more changes because I'm too lazy to clean them up: - fix chunk modification times - improve command line arguments - load multiple chunks at once
255 lines
5.5 KiB
Python
255 lines
5.5 KiB
Python
import string
|
|
import threading
|
|
import time
|
|
from utils import CHUNK_WIDTH, CHUNK_HEIGHT, Position
|
|
|
|
class ChunkDiff():
|
|
"""
|
|
Represents differences between two chunks (changes to be made to a chunk).
|
|
Can be used to transform a chunk into another chunk.
|
|
|
|
Todo: Implement delete diff
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._chars = {}
|
|
|
|
def __str__(self):
|
|
return "cd" + str(self._chars)
|
|
|
|
def __repr__(self):
|
|
return "cd" + repr(self._chars)
|
|
|
|
@classmethod
|
|
def from_dict(cls, d):
|
|
diff = cls()
|
|
diff._chars = {int(i): v for i, v in d.items()}
|
|
return diff
|
|
#self._chars = d.copy()
|
|
|
|
@classmethod
|
|
def from_string(cls, s):
|
|
diff = cls()
|
|
#for c in string
|
|
pass
|
|
|
|
def copy(self):
|
|
return ChunkDiff.from_dict(self.to_dict().copy())
|
|
|
|
def combine(self, diff):
|
|
newdiff = self.copy()
|
|
newdiff.apply(diff)
|
|
return newdiff
|
|
|
|
def to_dict(self):
|
|
return self._chars
|
|
#return self._chars.copy()
|
|
|
|
def set(self, x, y, character):
|
|
pos = x+y*CHUNK_WIDTH
|
|
self._chars[pos] = character
|
|
|
|
def delete(self, x, y):
|
|
self.set(x, y, " ")
|
|
|
|
def clear_deletions(self):
|
|
self._chars = {i: v for i, v in self._chars.items() if v != " "}
|
|
|
|
def apply(self, diff):
|
|
for i, c in diff._chars.items():
|
|
self._chars[i] = c
|
|
|
|
def lines(self):
|
|
d = self._chars
|
|
s = "".join(d.get(i, " ") for i in range(CHUNK_WIDTH*CHUNK_HEIGHT))
|
|
return [s[i:i+CHUNK_WIDTH] for i in range(0, CHUNK_WIDTH*CHUNK_HEIGHT, CHUNK_WIDTH)]
|
|
|
|
def empty(self):
|
|
return not bool(self._chars)
|
|
|
|
def legitimate(self):
|
|
for i, char in self._chars.items():
|
|
if not (isinstance(char, str) and len(char) == 1 and ord(char) > 31 and (char not in string.whitespace or char == " ")):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def diff(self, chunk):
|
|
diffs = {}
|
|
for pos, char in self._chars.items():
|
|
diffs[pos] = chunk._chars.get(pos, " ")
|
|
|
|
return ChunkDiff.from_dict(diffs)
|
|
|
|
def jsonify_diffs(diffs):
|
|
ddiffs = []
|
|
for dchunk in diffs:
|
|
pos = dchunk[0]
|
|
ddiff = dchunk[1].to_dict()
|
|
ddiffs.append((pos, ddiff))
|
|
|
|
return ddiffs
|
|
|
|
def dejsonify_diffs(ddiffs):
|
|
diffs = []
|
|
for dchunk in ddiffs:
|
|
pos = Position(dchunk[0][0], dchunk[0][1])
|
|
diff = ChunkDiff.from_dict(dchunk[1])
|
|
diffs.append((pos, diff))
|
|
|
|
return diffs
|
|
|
|
class Chunk():
|
|
"""
|
|
Represents a chunk (16x8 characters on the map).
|
|
Is able to generate diffs
|
|
- from another chunk
|
|
- from direct changes
|
|
- from accumulated changes
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._content = ChunkDiff()
|
|
self._modifications = ChunkDiff()
|
|
|
|
self.last_modified = 0
|
|
|
|
def set(self, x, y, character):
|
|
self._modifications.set(x, y, character)
|
|
self.touch()
|
|
|
|
def delete(self, x, y):
|
|
self._modifications.delete(x, y)
|
|
self.touch()
|
|
|
|
def commit_changes(self):
|
|
self.commit_diff(self._modifications)
|
|
self._modifications = ChunkDiff()
|
|
self.touch()
|
|
|
|
def apply_diff(self, diff):
|
|
self._modifications.apply(diff)
|
|
self.touch()
|
|
|
|
def commit_diff(self, diff):
|
|
self._content.apply(diff)
|
|
self._content.clear_deletions()
|
|
self.touch()
|
|
|
|
def drop_changes(self):
|
|
self._modifications = ChunkDiff()
|
|
self.touch()
|
|
|
|
def get_changes(self):
|
|
return self._modifications
|
|
|
|
def as_diff(self):
|
|
return self._content.combine(self._modifications)
|
|
|
|
def touch(self, now=None):
|
|
self.last_modified = now or time.time()
|
|
|
|
def age(self, now=None):
|
|
return (now or time.time()) - self.last_modified
|
|
|
|
def lines(self):
|
|
return self.as_diff().lines()
|
|
|
|
def modified(self):
|
|
return not self._modifications.empty()
|
|
|
|
def empty(self):
|
|
return self._content.empty() and self._modifications.empty()
|
|
|
|
class ChunkPool():
|
|
"""
|
|
Is a collection of chunks.
|
|
Allows user to manage (get, modify, delete) chunks, keeps track of chunks for them.
|
|
Load chunks it doesn't know.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._chunks = {}
|
|
self._lock = threading.RLock()
|
|
|
|
def __enter__(self):
|
|
self._lock.acquire()
|
|
return self
|
|
|
|
def __exit__(self, type, value, tb):
|
|
self._lock.release()
|
|
|
|
def set(self, pos, chunk):
|
|
self._chunks[pos] = chunk
|
|
|
|
def get(self, pos):
|
|
return self._chunks.get(pos)
|
|
|
|
def create(self, pos):
|
|
chunk = Chunk()
|
|
self.set(pos, chunk)
|
|
return chunk
|
|
|
|
def apply_diffs(self, diffs):
|
|
for dchunk in diffs:
|
|
pos = dchunk[0]
|
|
diff = dchunk[1]
|
|
chunk = self.get(pos) or self.create(pos)
|
|
#chunk = self.load(pos)
|
|
|
|
if not diff.empty():
|
|
chunk.apply_diff(diff)
|
|
|
|
def commit_diffs(self, diffs):
|
|
for dchunk in diffs:
|
|
pos = dchunk[0]
|
|
diff = dchunk[1]
|
|
chunk = self.get(pos) or self.create(pos)
|
|
#chunk = self.load(pos)
|
|
|
|
if not diff.empty():
|
|
chunk.commit_diff(diff)
|
|
|
|
def commit_changes(self):
|
|
changes = []
|
|
|
|
for pos, chunk in self._chunks.items():
|
|
if chunk.modified():
|
|
changes.append((pos, chunk.get_changes()))
|
|
chunk.commit_changes()
|
|
|
|
return changes
|
|
|
|
def save_changes(self):
|
|
self.commit_changes()
|
|
|
|
def load(self, pos):
|
|
if not self.get(pos):
|
|
self.create(pos)
|
|
#return self.get(pos) or self.create(pos)
|
|
|
|
def load_list(self, coords):
|
|
for pos in coords:
|
|
self.load(pos)
|
|
|
|
def unload(self, pos):
|
|
if pos in self._chunks:
|
|
del self._chunks[pos]
|
|
|
|
def unload_list(self, coords):
|
|
for pos in coords:
|
|
self.unload(pos)
|
|
|
|
def clean_up(self, except_for=[], condition=lambda pos, chunk: True):
|
|
## old list comprehension which became too long:
|
|
#coords = [pos for pos, chunk in self._chunks.items() if not pos in except_for and condition(chunk)]
|
|
|
|
#self.save_changes() # needs to be accounted for by the user
|
|
|
|
coords = []
|
|
|
|
for pos, chunk in self._chunks.items():
|
|
if not pos in except_for and condition(pos, chunk):
|
|
coords.append(pos)
|
|
|
|
self.unload_list(coords)
|