aboutsummaryrefslogtreecommitdiffstats
path: root/idatui
diff options
context:
space:
mode:
Diffstat (limited to 'idatui')
-rw-r--r--idatui/app.py34
-rw-r--r--idatui/domain.py17
2 files changed, 39 insertions, 12 deletions
diff --git a/idatui/app.py b/idatui/app.py
index abdaa1e..20aed88 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -2052,10 +2052,31 @@ class HexView(ScrollView, can_focus=True):
if data is None:
segs.append(Segment("… fetching", _S_DIM))
else:
+ # Emit RUNS, not one segment per byte. A row is 32 cells whose style
+ # almost never changes (one cursor cell, or a trace boundary), and a
+ # segment per cell made every hex frame 1540 segments for the
+ # compositor to cut and merge again.
n = len(data)
+ run: list[str] = []
+ run_st = None
+
+ def flush(st=None, _segs=segs) -> None:
+ nonlocal run, run_st
+ if run:
+ _segs.append(Segment("".join(run), run_st))
+ run = []
+ run_st = st
+
+ def put(text: str, st) -> None:
+ nonlocal run_st
+ if st is not run_st:
+ flush(st)
+ run.append(text)
+
+ run_st = _S_HEX
for i in range(16):
if i == 8:
- segs.append(Segment(" ", _S_HEX))
+ put(" ", _S_HEX)
if i < n:
live = tknown is not None and tknown[i]
val = tmem[i] if live else data[i]
@@ -2065,10 +2086,10 @@ class HexView(ScrollView, can_focus=True):
st = _S_HEX
else:
st = _S_HEX_LIVE if live else _S_HEX_STALE
- segs.append(Segment(f"{val:02X} ", st))
+ put(f"{val:02X} ", st)
else:
- segs.append(Segment(" ", _S_HEX))
- segs.append(Segment(" |", _S_DIM))
+ put(" ", _S_HEX)
+ put(" |", _S_DIM)
for i in range(16):
if i < n:
live = tknown is not None and tknown[i]
@@ -2082,8 +2103,9 @@ class HexView(ScrollView, can_focus=True):
st = _S_HEX_LIVE if live else _S_HEX_STALE
else:
ch, st = " ", _S_ASCII
- segs.append(Segment(ch, st))
- segs.append(Segment("|", _S_DIM))
+ put(ch, st)
+ put("|", _S_DIM)
+ flush()
return Strip(segs).adjust_cell_length(width, _S_HEX)
diff --git a/idatui/domain.py b/idatui/domain.py
index 9dea353..e107786 100644
--- a/idatui/domain.py
+++ b/idatui/domain.py
@@ -28,6 +28,7 @@ import threading
import urllib.request
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field, replace
+from typing import NamedTuple
from typing import Callable, TYPE_CHECKING
from . import diag
@@ -90,15 +91,19 @@ class Line:
)
-@dataclass(frozen=True, slots=True)
-class Head:
+class Head(NamedTuple):
"""One flat-listing item (from the ``heads`` server tool): a code
instruction, a data item, or an undefined byte run.
- ``slots=True`` because this is the most-constructed object in the codebase:
- a jump to an address near the end of a big binary builds one per listing row
- it walks past, hundreds of thousands of them, and the slotted layout is ~20%
- cheaper to build (and smaller to hold).
+ A ``NamedTuple`` rather than a dataclass because this is by far the
+ most-constructed object in the codebase -- a jump to an address near the end
+ of a big binary builds one per listing row it walks past, a quarter of a
+ million of them -- and ``tuple.__new__`` costs 1.9us where a frozen
+ dataclass's ``__init__`` costs 2.9us. Attribute reads are marginally slower
+ (10ns vs 20ns), which is the right trade: rows are built far more often than
+ they are read, and a viewport only ever reads forty of them.
+
+ Immutable, like the frozen dataclass it replaced.
"""
ea: int