`FastAppendCurve`: Only render in-view data if possible

More or less this improves update latency like mad. Only draw data in
view and avoid full path regen as much as possible within a given
(down)sampling setting. We now support append path updates with in-view
data and the *SPECIAL CAVEAT* is that we avoid redrawing the whole curve
**only when** we calc an `append_length <= 1` **even if the view range
changed**. XXX: this should change in the future probably such that the
caller graphics update code can pass a flag which says whether or not to
do a full redraw based on it knowing where it's an interaction based
view-range change or a flow update change which doesn't require a full
path re-render.
incremental_update_paths
Tyler Goodlet 2022-04-22 13:59:20 -04:00
parent 2af4050e5e
commit c94c53286b
1 changed files with 69 additions and 45 deletions

View File

@ -138,6 +138,7 @@ class FastAppendCurve(pg.GraphicsObject):
# brutaaalll, see comments within.. # brutaaalll, see comments within..
self._y = self.yData = y self._y = self.yData = y
self._x = self.xData = x self._x = self.xData = x
self._vr: Optional[tuple] = None
self._name = name self._name = name
self.path: Optional[QtGui.QPainterPath] = None self.path: Optional[QtGui.QPainterPath] = None
@ -287,6 +288,17 @@ class FastAppendCurve(pg.GraphicsObject):
istart, istop = self._xrange istart, istop = self._xrange
else: else:
self._xrange = istart, istop = x[0], x[-1] self._xrange = istart, istop = x[0], x[-1]
# compute the length diffs between the first/last index entry in
# the input data and the last indexes we have on record from the
# last time we updated the curve index.
prepend_length = int(istart - x[0])
append_length = int(x[-1] - istop)
# this is the diff-mode, "data"-rendered index
# tracking var..
self._xrange = x[0], x[-1]
# print(f"xrange: {self._xrange}") # print(f"xrange: {self._xrange}")
# XXX: lol brutal, the internals of `CurvePoint` (inherited by # XXX: lol brutal, the internals of `CurvePoint` (inherited by
@ -295,37 +307,36 @@ class FastAppendCurve(pg.GraphicsObject):
# self.yData = y # self.yData = y
# self._x, self._y = x, y # self._x, self._y = x, y
if view_range:
profiler(f'view range slice {view_range}')
# downsampling incremental state checking # downsampling incremental state checking
uppx = self.x_uppx() uppx = self.x_uppx()
px_width = self.px_width() px_width = self.px_width()
uppx_diff = (uppx - self._last_uppx) uppx_diff = (uppx - self._last_uppx)
new_sample_rate = False
should_ds = False should_ds = False
showing_src_data = self._in_ds
should_redraw = False should_redraw = False
# if a view range is passed, plan to draw the # if a view range is passed, plan to draw the
# source ouput that's "in view" of the chart. # source ouput that's "in view" of the chart.
if view_range and not self._in_ds: if (
view_range
# and not self._in_ds
# and not prepend_length > 0
):
# print(f'{self._name} vr: {view_range}') # print(f'{self._name} vr: {view_range}')
# by default we only pull data up to the last (current) index # by default we only pull data up to the last (current) index
x_out, y_out = x_iv[:-1], y_iv[:-1] x_out, y_out = x_iv[:-1], y_iv[:-1]
profiler(f'view range slice {view_range}')
# step mode: draw flat top discrete "step" if (
# over the index space for each datum. view_range != self._vr
if self._step_mode: and append_length > 1
# TODO: numba this bish ):
x_out, y_out = step_path_arrays_from_1d( should_redraw = True
x_out,
y_out
)
profiler('generated step arrays')
should_redraw = True self._vr = view_range
profiler('sliced in-view array history')
# x_last = x_iv[-1] # x_last = x_iv[-1]
# y_last = y_iv[-1] # y_last = y_iv[-1]
@ -335,7 +346,15 @@ class FastAppendCurve(pg.GraphicsObject):
# flip_cache = True # flip_cache = True
else: else:
self._xrange = x[0], x[-1] # if (
# not view_range
# or self._in_ds
# ):
# by default we only pull data up to the last (current) index
x_out, y_out = x[:-1], y[:-1]
if prepend_length > 0:
should_redraw = True
# check for downsampling conditions # check for downsampling conditions
if ( if (
@ -350,6 +369,8 @@ class FastAppendCurve(pg.GraphicsObject):
f'{self._name} sampler change: {self._last_uppx} -> {uppx}' f'{self._name} sampler change: {self._last_uppx} -> {uppx}'
) )
self._last_uppx = uppx self._last_uppx = uppx
new_sample_rate = True
showing_src_data = False
should_ds = True should_ds = True
elif ( elif (
@ -360,49 +381,47 @@ class FastAppendCurve(pg.GraphicsObject):
# source data so we clear our path data in prep # source data so we clear our path data in prep
# to generate a new one from original source data. # to generate a new one from original source data.
should_redraw = True should_redraw = True
new_sample_rate = True
should_ds = False should_ds = False
showing_src_data = True
# compute the length diffs between the first/last index entry in
# the input data and the last indexes we have on record from the
# last time we updated the curve index.
prepend_length = int(istart - x[0])
append_length = int(x[-1] - istop)
# no_path_yet = self.path is None # no_path_yet = self.path is None
if ( if (
self.path is None self.path is None
or should_redraw or should_redraw
or should_ds or new_sample_rate
or prepend_length > 0 or prepend_length > 0
): ):
if ( # if (
not view_range # not view_range
or self._in_ds # or self._in_ds
): # ):
# by default we only pull data up to the last (current) index # # by default we only pull data up to the last (current) index
x_out, y_out = x[:-1], y[:-1] # x_out, y_out = x[:-1], y[:-1]
# step mode: draw flat top discrete "step" # step mode: draw flat top discrete "step"
# over the index space for each datum. # over the index space for each datum.
if self._step_mode: if self._step_mode:
x_out, y_out = step_path_arrays_from_1d( x_out, y_out = step_path_arrays_from_1d(
x_out, x_out,
y_out, y_out,
) )
# TODO: numba this bish # TODO: numba this bish
profiler('generated step arrays') profiler('generated step arrays')
if should_redraw: if should_redraw:
profiler('path reversion to non-ds')
if self.path: if self.path:
# print(f'CLEARING PATH {self._name}')
self.path.clear() self.path.clear()
if self.fast_path: if self.fast_path:
self.fast_path.clear() self.fast_path.clear()
if should_redraw and not should_ds: profiler('cleared paths due to `should_redraw` set')
if self._in_ds:
log.info(f'DEDOWN -> {self._name}') if new_sample_rate and showing_src_data:
# if self._in_ds:
log.info(f'DEDOWN -> {self._name}')
self._in_ds = False self._in_ds = False
@ -423,7 +442,12 @@ class FastAppendCurve(pg.GraphicsObject):
finiteCheck=False, finiteCheck=False,
path=self.path, path=self.path,
) )
profiler('generated fresh path') profiler(
'generated fresh path\n'
f'should_redraw: {should_redraw}\n'
f'should_ds: {should_ds}\n'
f'new_sample_rate: {new_sample_rate}\n'
)
# profiler(f'DRAW PATH IN VIEW -> {self._name}') # profiler(f'DRAW PATH IN VIEW -> {self._name}')
# reserve mem allocs see: # reserve mem allocs see:
@ -455,7 +479,7 @@ class FastAppendCurve(pg.GraphicsObject):
elif ( elif (
append_length > 0 append_length > 0
and not view_range # and not view_range
): ):
new_x = x[-append_length - 2:-1] new_x = x[-append_length - 2:-1]
new_y = y[-append_length - 2:-1] new_y = y[-append_length - 2:-1]
@ -696,7 +720,7 @@ class FastAppendCurve(pg.GraphicsObject):
if path: if path:
p.drawPath(path) p.drawPath(path)
profiler('.drawPath(path)') profiler(f'.drawPath(path): {path.capacity()}')
fp = self.fast_path fp = self.fast_path
if fp: if fp: