scripts: dbgrbyd.py: Moved jump/lifetime renderers into own classes

This just organizes things a bit better and makes dbg_log less of a
monolith:

- JumpArt - Encapsulates ascii jump rendering (-j/--jumps)

- LifetimeArt - Encapsulates ascii lifetime rendering (-g/--lifetimes)
This commit is contained in:
Christopher Haster
2025-04-01 03:23:24 -05:00
parent 002c2ea1e6
commit 86055fc989
+205 -81
View File
@@ -1101,20 +1101,60 @@ class Rbyd:
return self._tree_rtree(**args) return self._tree_rtree(**args)
# show the rbyd log
def dbg_log(rbyd, *,
block_size,
color=False,
**args):
data = rbyd.data
# preprocess jumps # jump renderer
if args.get('jumps'): class JumpArt:
# abstract thing for jump rendering
class Jump(co.namedtuple('Jump', ['a', 'b', 'x', 'color'])):
__slots__ = ()
def __new__(cls, a, b, x=0, color='b'):
return super().__new__(cls, a, b, x, color)
def __repr__(self):
return '%s(%s, %s, %s, %s)' % (
self.__class__.__name__,
self.a,
self.b,
self.x,
self.color)
# don't include color in branch comparisons, or else our tree
# renderings can end up with inconsistent colors between runs
def __eq__(self, other):
return (self.a, self.b, self.x) == (other.a, other.b, other.x)
def __ne__(self, other):
return (self.a, self.b, self.x) != (other.a, other.b, other.x)
def __hash__(self):
return hash((self.a, self.b, self.x))
def __init__(self, jumps):
self.jumps = jumps
self.width = 2*max((x for _, _, x, _ in jumps), default=0)
def collide(self):
# figure out x-offsets to avoid collisions between jumps
for j in range(len(self.jumps)):
a, b, _, c = self.jumps[j]
x = 0
while any(
max(a, b) >= min(a_, b_)
and max(a_, b_) >= min(a, b)
and x == x_
for a_, b_, x_, _ in self.jumps[:j]):
x += 1
self.jumps[j] = self.Jump(a, b, x, c)
@classmethod
def fromrbyd(cls, rbyd, all=False):
all_ = all; del all
jumps = [] jumps = []
j_ = 4 j_ = 4
while j_ < (block_size if args.get('all') else rbyd.eoff): while j_ < (len(rbyd.data) if all_ else rbyd.eoff):
j = j_ j = j_
v, tag, w, size, d = fromtag(data[j_:]) v, tag, w, size, d = fromtag(rbyd.data[j_:])
j_ += d j_ += d
if not tag & TAG_ALT: if not tag & TAG_ALT:
j_ += size j_ += size
@@ -1122,30 +1162,22 @@ def dbg_log(rbyd, *,
if tag & TAG_ALT and size: if tag & TAG_ALT and size:
# figure out which alt color # figure out which alt color
if tag & TAG_R: if tag & TAG_R:
_, ntag, _, _, _ = fromtag(data[j_:]) _, ntag, _, _, _ = fromtag(rbyd.data[j_:])
if ntag & TAG_R: if ntag & TAG_R:
jumps.append((j, j-size, 0, 'y')) jumps.append(cls.Jump(j, j-size, 0, 'y'))
else: else:
jumps.append((j, j-size, 0, 'r')) jumps.append(cls.Jump(j, j-size, 0, 'r'))
else: else:
jumps.append((j, j-size, 0, 'b')) jumps.append(cls.Jump(j, j-size, 0, 'b'))
# figure out x-offsets to avoid collisions between jumps jumpart = cls(jumps)
for j in range(len(jumps)): jumpart.collide()
a, b, _, c = jumps[j] return jumpart
x = 0
while any(
max(a, b) >= min(a_, b_)
and max(a_, b_) >= min(a, b)
and x == x_
for a_, b_, x_, _ in jumps[:j]):
x += 1
jumps[j] = a, b, x, c
def jumprepr(j): def repr(self, j, color=False):
# render jumps # render jumps
chars = {} chars = {}
for a, b, x, c in jumps: for a, b, x, c in self.jumps:
c_start = ( c_start = (
'\x1b[33m' if color and c == 'y' '\x1b[33m' if color and c == 'y'
else '\x1b[31m' if color and c == 'r' else '\x1b[31m' if color and c == 'r'
@@ -1168,17 +1200,22 @@ def dbg_log(rbyd, *,
return ''.join(chars.get(x, ' ') return ''.join(chars.get(x, ' ')
for x in range(max(chars.keys(), default=0)+1)) for x in range(max(chars.keys(), default=0)+1))
# preprocess lifetimes
lifetime_width = 0 # lifetime renderer
if args.get('lifetimes'): class LifetimeArt:
class Lifetime: # abstract things for lifetime rendering
color_i = 0 class Lifetime(co.namedtuple('Lifetime', ['id', 'origin', 'tags'])):
def __init__(self, j): __slots__ = ()
self.origin = j def __new__(cls, id, origin, tags=None):
self.tags = set() return super().__new__(cls, id, origin,
self.color = COLORS[self.__class__.color_i] set(tags) if tags is not None else set())
self.__class__.color_i = (
self.__class__.color_i + 1) % len(COLORS) def __repr__(self):
return '%s(%s, %s, %s)' % (
self.__class__.__name__,
self.id,
self.origin,
self.tags)
def add(self, j): def add(self, j):
self.tags.add(j) self.tags.add(j)
@@ -1186,10 +1223,75 @@ def dbg_log(rbyd, *,
def __bool__(self): def __bool__(self):
return bool(self.tags) return bool(self.tags)
# define equality by id
def __eq__(self, other):
return self.id == other.id
# first figure out where each rid comes from def __ne__(self, other):
weights = [] return self.id != other.id
lifetimes = []
def __hash__(self):
return hash((self.id))
def __lt__(self, other):
return self.id < other.id
def __le__(self, other):
return self.id <= other.id
def __gt__(self, other):
return self.id > other.id
def __ge__(self, other):
return self.id >= other.id
class Checkpoint(co.namedtuple('Checkpoint', [
'j', 'weights', 'lifetimes', 'grows', 'shrinks', 'tags'])):
__slots__ = ()
def __new__(cls, j, weights, lifetimes,
grows=None, shrinks=None, tags=None):
return super().__new__(cls, j,
# note we rely on tuple making frozen copies here
tuple(weights),
tuple(lifetimes),
frozenset(grows) if grows is not None else frozenset(),
frozenset(shrinks) if shrinks is not None else frozenset(),
frozenset(tags) if tags is not None else frozenset())
# define equality by checkpoint offset
def __eq__(self, other):
return self.j == other.j
def __ne__(self, other):
return self.j != other.j
def __hash__(self):
return hash((self.j))
def __lt__(self, other):
return self.j < other.j
def __le__(self, other):
return self.j <= other.j
def __gt__(self, other):
return self.j > other.j
def __ge__(self, other):
return self.j >= other.j
def __init__(self, checkpoints):
self.lifetimes = sorted(set(
lifetime
for checkpoint in checkpoints
for lifetime in checkpoint.lifetimes))
self.checkpoints = checkpoints
self.width = 2*max(
(sum(1 for lifetime in checkpoint.lifetimes if lifetime)
for checkpoint in checkpoints),
default=0)
@staticmethod
def index(weights, rid): def index(weights, rid):
for i, w in enumerate(weights): for i, w in enumerate(weights):
if rid < w: if rid < w:
@@ -1197,21 +1299,23 @@ def dbg_log(rbyd, *,
rid -= w rid -= w
return len(weights), 0 return len(weights), 0
checkpoint_js = [0] @classmethod
checkpoints = [([], [], set(), set(), set())] def fromrbyd(cls, rbyd, all=False):
def checkpoint(j, weights, lifetimes, grows, shrinks, tags): all_ = all; del all
checkpoint_js.append(j)
checkpoints.append(( # first figure out where each rid comes from
weights.copy(), lifetimes.copy(), id = 0
grows, shrinks, tags)) weights = []
lifetimes = []
checkpoints = [cls.Checkpoint(0, [], [])]
lower_, upper_ = 0, 0 lower_, upper_ = 0, 0
weight_ = 0 weight_ = 0
trunk_ = 0 trunk_ = 0
j_ = 4 j_ = 4
while j_ < (block_size if args.get('all') else rbyd.eoff): while j_ < (len(rbyd.data) if all_ else rbyd.eoff):
j = j_ j = j_
v, tag, w, size, d = fromtag(data[j_:]) v, tag, w, size, d = fromtag(rbyd.data[j_:])
j_ += d j_ += d
if not tag & TAG_ALT: if not tag & TAG_ALT:
j_ += size j_ += size
@@ -1238,20 +1342,24 @@ def dbg_log(rbyd, *,
# note we ignore out-of-bounds here for debugging # note we ignore out-of-bounds here for debugging
if delta > 0: if delta > 0:
# grow lifetimes # grow lifetimes
i, rid_ = index(weights, lower_) l = cls.Lifetime(id, j)
id += 1
i, rid_ = cls.index(weights, lower_)
if rid_ > 0: if rid_ > 0:
weights[i:i+1] = [rid_, delta, weights[i]-rid_] weights[i:i+1] = [rid_, delta, weights[i]-rid_]
lifetimes[i:i+1] = [ lifetimes[i:i+1] = [lifetimes[i], l, lifetimes[i]]
lifetimes[i], Lifetime(j), lifetimes[i]]
else: else:
weights[i:i] = [delta] weights[i:i] = [delta]
lifetimes[i:i] = [Lifetime(j)] lifetimes[i:i] = [l]
checkpoint(j, weights, lifetimes, {i}, set(), {i}) checkpoints.append(cls.Checkpoint(
j, weights, lifetimes,
grows={i},
tags={i}))
elif delta < 0: elif delta < 0:
# shrink lifetimes # shrink lifetimes
i, rid_ = index(weights, lower_) i, rid_ = cls.index(weights, lower_)
delta_ = -delta delta_ = -delta
weights_ = weights.copy() weights_ = weights.copy()
lifetimes_ = lifetimes.copy() lifetimes_ = lifetimes.copy()
@@ -1269,28 +1377,30 @@ def dbg_log(rbyd, *,
lifetimes_[i:i+1] = [] lifetimes_[i:i+1] = []
shrinks.add(i + len(shrinks)) shrinks.add(i + len(shrinks))
checkpoint(j, weights, lifetimes, set(), shrinks, {i}) checkpoints.append(cls.Checkpoint(
j, weights, lifetimes,
shrinks=shrinks,
tags={i}))
weights = weights_ weights = weights_
lifetimes = lifetimes_ lifetimes = lifetimes_
if rid >= 0: if rid >= 0:
# attach tag to lifetime # attach tag to lifetime
i, rid_ = index(weights, rid) i, rid_ = cls.index(weights, rid)
if i < len(weights): if i < len(weights):
lifetimes[i].add(j) lifetimes[i].add(j)
if delta == 0: if delta == 0:
checkpoint(j, weights, lifetimes, set(), set(), {i}) checkpoints.append(cls.Checkpoint(
j, weights, lifetimes,
tags={i}))
lifetime_width = 2*max(( return cls(checkpoints)
sum(1 for lifetime in lifetimes if lifetime)
for _, lifetimes, _, _, _ in checkpoints),
default=0)
def lifetimerepr(j): def repr(self, j, color=False):
x = bisect.bisect(checkpoint_js, j)-1 i = bisect.bisect(self.checkpoints, j,
j_ = checkpoint_js[x] key=lambda checkpoint: checkpoint.j) - 1
weights, lifetimes, grows, shrinks, tags = checkpoints[x] j_, weights, lifetimes, grows, shrinks, tags = self.checkpoints[i]
reprs = [] reprs = []
colors = [] colors = []
@@ -1299,8 +1409,7 @@ def dbg_log(rbyd, *,
# skip lifetimes with no tags and shrinks # skip lifetimes with no tags and shrinks
if not lifetime or (j != j_ and i in shrinks): if not lifetime or (j != j_ and i in shrinks):
if i in grows or i in shrinks or i in tags: if i in grows or i in shrinks or i in tags:
tags = tags.copy() tags = tags | {i+1}
tags.add(i+1)
continue continue
if j == j_ and i in grows: if j == j_ and i in grows:
@@ -1318,7 +1427,7 @@ def dbg_log(rbyd, *,
else: else:
reprs.append('| ') reprs.append('| ')
colors.append(lifetime.color) colors.append(COLORS[lifetime.id % len(COLORS)])
return '%s%*s' % ( return '%s%*s' % (
''.join('%s%s%s' % ( ''.join('%s%s%s' % (
@@ -1326,18 +1435,36 @@ def dbg_log(rbyd, *,
r, r,
'\x1b[m' if color else '') '\x1b[m' if color else '')
for r, c in zip(reprs, colors)), for r, c in zip(reprs, colors)),
lifetime_width - sum(len(r) for r in reprs), '') self.width - sum(len(r) for r in reprs), '')
# show the rbyd log
def dbg_log(rbyd, *,
color=False,
**args):
data = rbyd.data
# preprocess jumps
if args.get('jumps'):
jumpart = JumpArt.fromrbyd(rbyd,
all=args.get('all'))
# preprocess lifetimes
l_width = 0
if args.get('lifetimes'):
lifetimeart = LifetimeArt.fromrbyd(rbyd, all=args.get('all'))
l_width = lifetimeart.width
# dynamically size the id field # dynamically size the id field
# #
# we need to do an additional pass to find this since our rbyd weight # we need to do an additional pass to find this since our rbyd weight
# does not include any shrub trees # does not include any shrub trees
data = rbyd.data
weight_ = 0 weight_ = 0
weight__ = 0 weight__ = 0
trunk_ = 0 trunk_ = 0
j_ = 4 j_ = 4
while j_ < (block_size if args.get('all') else rbyd.eoff): while j_ < (len(data) if args.get('all') else rbyd.eoff):
j = j_ j = j_
v, tag, w, size, d = fromtag(data[j_:]) v, tag, w, size, d = fromtag(data[j_:])
j_ += d j_ += d
@@ -1364,7 +1491,7 @@ def dbg_log(rbyd, *,
if args.get('raw'): if args.get('raw'):
print('%8s: %*s%*s %s' % ( print('%8s: %*s%*s %s' % (
'%04x' % 0, '%04x' % 0,
lifetime_width, '', l_width, '',
2*w_width+1, '', 2*w_width+1, '',
next(xxd(data[0:4])))) next(xxd(data[0:4]))))
@@ -1375,7 +1502,7 @@ def dbg_log(rbyd, *,
lower_, upper_ = 0, 0 lower_, upper_ = 0, 0
trunk_ = 0 trunk_ = 0
j_ = 4 j_ = 4
while j_ < (block_size if args.get('all') else rbyd.eoff): while j_ < (len(data) if args.get('all') else rbyd.eoff):
notes = [] notes = []
# read next tag # read next tag
@@ -1428,7 +1555,7 @@ def dbg_log(rbyd, *,
'\x1b[90m' if color and j >= rbyd.eoff else '', '\x1b[90m' if color and j >= rbyd.eoff else '',
j, j,
'\x1b[m' if color and j >= rbyd.eoff else '', '\x1b[m' if color and j >= rbyd.eoff else '',
lifetime_width, lifetimerepr(j) l_width, lifetimeart.repr(j, color)
if args.get('lifetimes') if args.get('lifetimes')
else '', else '',
'\x1b[90m' if color and j >= rbyd.eoff else '', '\x1b[90m' if color and j >= rbyd.eoff else '',
@@ -1444,7 +1571,7 @@ def dbg_log(rbyd, *,
else ''), else ''),
' (%s)' % ', '.join(notes) if notes else '', ' (%s)' % ', '.join(notes) if notes else '',
'\x1b[m' if color and j >= rbyd.eoff else '', '\x1b[m' if color and j >= rbyd.eoff else '',
' %s' % jumprepr(j) ' %s' % jumpart.repr(j, color)
if args.get('jumps') and not notes if args.get('jumps') and not notes
else '')) else ''))
@@ -1454,7 +1581,7 @@ def dbg_log(rbyd, *,
print('%s%8s: %*s%*s %s%s' % ( print('%s%8s: %*s%*s %s%s' % (
'\x1b[90m' if color and j >= rbyd.eoff else '', '\x1b[90m' if color and j >= rbyd.eoff else '',
'%04x' % (j + o*16), '%04x' % (j + o*16),
lifetime_width, '', l_width, '',
2*w_width+1, '', 2*w_width+1, '',
line, line,
'\x1b[m' if color and j >= rbyd.eoff else '')) '\x1b[m' if color and j >= rbyd.eoff else ''))
@@ -1464,14 +1591,13 @@ def dbg_log(rbyd, *,
print('%s%8s: %*s%*s %s%s' % ( print('%s%8s: %*s%*s %s%s' % (
'\x1b[90m' if color and j >= rbyd.eoff else '', '\x1b[90m' if color and j >= rbyd.eoff else '',
'%04x' % (j+d + o*16), '%04x' % (j+d + o*16),
lifetime_width, '', l_width, '',
2*w_width+1, '', 2*w_width+1, '',
line, line,
'\x1b[m' if color and j >= rbyd.eoff else '')) '\x1b[m' if color and j >= rbyd.eoff else ''))
# show the rbyd tree # show the rbyd tree
def dbg_tree(rbyd, *, def dbg_tree(rbyd, *,
block_size,
color=False, color=False,
**args): **args):
if not rbyd: if not rbyd:
@@ -1585,12 +1711,10 @@ def main(disk, blocks=None, *,
if args.get('log'): if args.get('log'):
dbg_log(rbyd, dbg_log(rbyd,
block_size=block_size,
color=color, color=color,
**args) **args)
else: else:
dbg_tree(rbyd, dbg_tree(rbyd,
block_size=block_size,
color=color, color=color,
**args) **args)