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
|
_in_ds: bool = False
|
||||||
_index_step: float | None = None
|
_index_step: float | None = None
|
||||||
_time_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)
|
# map from uppx -> (downsampled data, incremental graphics)
|
||||||
_src_r: Renderer | None = None
|
_src_r: Renderer | None = None
|
||||||
|
|
|
||||||
|
|
@ -101,10 +101,36 @@ def update_fsp_chart(
|
||||||
)
|
)
|
||||||
return
|
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
|
# update graphics
|
||||||
# NOTE: this does a length check internally which allows it
|
# NOTE: this does a length check internally which allows it
|
||||||
# staying above the last row check below..
|
# staying above the last row check below..
|
||||||
viz.update_graphics(force_redraw=force_redraw)
|
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
|
# XXX: re: ``array_key``: fsp func names must be unique meaning we
|
||||||
# can't have duplicates of the underlying data even if multiple
|
# 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.data.ticktools import FeedQuote
|
||||||
from piker.fsp._api import Fsp
|
from piker.fsp._api import Fsp
|
||||||
from piker.ui._chart import LinkedSplits
|
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 (
|
from tractor.ipc._shm import (
|
||||||
NDToken,
|
NDToken,
|
||||||
ShmArray,
|
ShmArray,
|
||||||
|
|
@ -502,6 +505,58 @@ class Viz:
|
||||||
self.force_redraws.append(force_redraw)
|
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:
|
def test_fsp_history_update_redraws_only_derived_vizs() -> None:
|
||||||
'''
|
'''
|
||||||
Keep a source-history prepend from refreshing the whole chart.
|
Keep a source-history prepend from refreshing the whole chart.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue