Skip unchanged realtime FSP redraws
Cache destination bounds and timestamp/value bytes on `Viz`. Skip identical quote-driven refreshes, including stable NaNs, while changed samples and forced history repairs still redraw. Add a regression covering each invalidation path. (this commit msg was generated in some part by `codex` using `gpt-6` (`openai`))wkt/fsp_backfill_sync
parent
20406210b3
commit
5b4622fb39
|
|
@ -319,6 +319,7 @@ class Viz(Struct):
|
|||
_in_ds: bool = False
|
||||
_index_step: float | None = None
|
||||
_time_step: float | None = None
|
||||
_last_fsp_update_sig: tuple[int, int, bytes]|None = None
|
||||
|
||||
# map from uppx -> (downsampled data, incremental graphics)
|
||||
_src_r: Renderer | None = None
|
||||
|
|
|
|||
|
|
@ -101,10 +101,36 @@ def update_fsp_chart(
|
|||
)
|
||||
return
|
||||
|
||||
# `update_fsp_chart()` runs for every source quote, while an FSP can
|
||||
# publish more slowly. `Viz._last_fsp_update_sig` combines the
|
||||
# destination's absolute half-open `ShmArray` bounds with bitwise
|
||||
# `last_row['time']` and `last_row[array_key]` scalars. Comparing
|
||||
# bytes keeps repeated NaNs stable; scalar equality treats every NaN
|
||||
# as changed. A changed `ShmArray._first.value` catches history
|
||||
# prepends, a changed `ShmArray._last.value` catches sample appends,
|
||||
# and changed row bytes catch mutation of the current sample.
|
||||
# `force_redraw` bypasses this gate because an equal-bound gap repair
|
||||
# can rewrite earlier rows without changing the current row. This is
|
||||
# only a repaint key, not synchronization proof; a mixed SHM read
|
||||
# merely causes one extra later redraw.
|
||||
update_sig: tuple[int, int, bytes] = (
|
||||
shm._first.value,
|
||||
shm._last.value,
|
||||
np.asarray(last_row['time']).tobytes()
|
||||
+ np.asarray(last_row[array_key]).tobytes(),
|
||||
)
|
||||
if (
|
||||
not force_redraw
|
||||
and
|
||||
viz._last_fsp_update_sig == update_sig
|
||||
):
|
||||
return
|
||||
|
||||
# update graphics
|
||||
# NOTE: this does a length check internally which allows it
|
||||
# staying above the last row check below..
|
||||
viz.update_graphics(force_redraw=force_redraw)
|
||||
viz._last_fsp_update_sig = update_sig
|
||||
|
||||
# XXX: re: ``array_key``: fsp func names must be unique meaning we
|
||||
# can't have duplicates of the underlying data even if multiple
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ from piker.data._sharedmem import NDTokenMsg
|
|||
from piker.data.ticktools import FeedQuote
|
||||
from piker.fsp._api import Fsp
|
||||
from piker.ui._chart import LinkedSplits
|
||||
from piker.ui._fsp import update_fsp_vizs
|
||||
from piker.ui._fsp import (
|
||||
update_fsp_chart,
|
||||
update_fsp_vizs,
|
||||
)
|
||||
from tractor.ipc._shm import (
|
||||
NDToken,
|
||||
ShmArray,
|
||||
|
|
@ -502,6 +505,58 @@ class Viz:
|
|||
self.force_redraws.append(force_redraw)
|
||||
|
||||
|
||||
def test_fsp_chart_skips_unchanged_realtime_state() -> None:
|
||||
'''
|
||||
Avoid repainting an unchanged current FSP datum on every quote.
|
||||
|
||||
The display loop calls `update_fsp_chart()` for every realtime
|
||||
quote, even when a given FSP destination has not advanced or
|
||||
changed its current value. That rebuilt and painted the same last
|
||||
step rectangle repeatedly. Exercise identical state, an in-place
|
||||
value update, a new sample with the same value, and a forced
|
||||
history repair. The update counts prove unchanged requests are
|
||||
skipped while each legitimate invalidation still redraws. A final
|
||||
stable-NaN pair proves scalar NaN inequality can not defeat the
|
||||
guard.
|
||||
|
||||
'''
|
||||
shm = FspShm()
|
||||
viz = Viz('flow', shm)
|
||||
|
||||
update_fsp_chart(viz, 'flow', 'flow')
|
||||
update_fsp_chart(viz, 'flow', 'flow')
|
||||
assert viz.updates == 1
|
||||
|
||||
shm._array['flow'][shm._last.value - 1] = 42
|
||||
update_fsp_chart(viz, 'flow', 'flow')
|
||||
assert viz.updates == 2
|
||||
|
||||
shm._array['flow'][shm._last.value] = 42
|
||||
shm._last.value += 1
|
||||
update_fsp_chart(viz, 'flow', 'flow')
|
||||
assert viz.updates == 3
|
||||
|
||||
update_fsp_chart(
|
||||
viz,
|
||||
'flow',
|
||||
'flow',
|
||||
force_redraw=True,
|
||||
)
|
||||
assert viz.updates == 4
|
||||
|
||||
shm._array['flow'][shm._last.value - 1] = np.nan
|
||||
update_fsp_chart(viz, 'flow', 'flow')
|
||||
update_fsp_chart(viz, 'flow', 'flow')
|
||||
assert viz.updates == 5
|
||||
assert viz.force_redraws == [
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
]
|
||||
|
||||
|
||||
def test_fsp_history_update_redraws_only_derived_vizs() -> None:
|
||||
'''
|
||||
Keep a source-history prepend from refreshing the whole chart.
|
||||
|
|
|
|||
Loading…
Reference in New Issue