Pass display state table to interaction handlers
This took a teensie bit of reworking in some `.ui` modules more or less in the following order of functional dependence: - add a `Ctl-R` kb-binding to trigger a `Viz.reset_graphics()` in the kb-handler task `handle_viewmode_kb_inputs()`. - call the new method on all `Viz`s (& for all sample-rates) and `DisplayState` refs provided in a (new input) `dss: dict[str, DisplayState]` table, which was originally inite-ed from the multi-feed display loop (so orig in `.graphics_update_loop()` but now provided as an input to that func, see below..) - `._interaction`: allow binding in `async_handler()` kwargs (`via a `functools.partial`) passed to `ChartView.open_async_input_handler()` such that arbitrary inputs to our kb+mouse handler funcs can accept "wtv we desire". - use ^ to bind in the aforementioned `dss` display-state table to said handlers! - define the `dss` table (as mentioned) inside `._display.display_symbol_data()` and pass it into the update loop funcs as well as the newly augmented `.open_async_input_handler()` calls, - drop calling `chart.view.open_async_input_handler()` from the `.order_mode.open_order_mode()`'s enter block and instead factor it into the caller to support passing the `dss` table to the kb handlers. - comment out the original history update loop handling of forced `Viz` redraws entirely since we now have a manual method via `Ctl-R`. - now, just update the `._remote_ctl.dss: dict` with this table since we want to also provide rc for **all** loaded feeds, not just the currently shown one/set. - docs, naming and typing tweaks to `._event.open_handlers()`distribute_dis
parent
a7ad50cf8f
commit
c82ca812a8
|
@ -210,9 +210,9 @@ async def increment_history_view(
|
|||
):
|
||||
hist_chart: ChartPlotWidget = ds.hist_chart
|
||||
hist_viz: Viz = ds.hist_viz
|
||||
viz: Viz = ds.viz
|
||||
# viz: Viz = ds.viz
|
||||
assert 'hist' in hist_viz.shm.token['shm_name']
|
||||
name: str = hist_viz.name
|
||||
# name: str = hist_viz.name
|
||||
|
||||
# TODO: seems this is more reliable at keeping the slow
|
||||
# chart incremented in view more correctly?
|
||||
|
@ -225,7 +225,8 @@ async def increment_history_view(
|
|||
# draw everything from scratch on first entry!
|
||||
for curve_name, hist_viz in hist_chart._vizs.items():
|
||||
log.info(f'Forcing hard redraw -> {curve_name}')
|
||||
hist_viz.update_graphics(force_redraw=True)
|
||||
hist_viz.reset_graphics()
|
||||
# hist_viz.update_graphics(force_redraw=True)
|
||||
|
||||
async with open_sample_stream(1.) as min_istream:
|
||||
async for msg in min_istream:
|
||||
|
@ -248,27 +249,27 @@ async def increment_history_view(
|
|||
# - samplerd could emit the actual update range via
|
||||
# tuple and then we only enter the below block if that
|
||||
# range is detected as in-view?
|
||||
match msg:
|
||||
case {
|
||||
'backfilling': (viz_name, timeframe),
|
||||
} if (
|
||||
viz_name == name
|
||||
):
|
||||
log.warning(
|
||||
f'Forcing HARD REDRAW:\n'
|
||||
f'name: {name}\n'
|
||||
f'timeframe: {timeframe}\n'
|
||||
)
|
||||
# TODO: only allow this when the data is IN VIEW!
|
||||
# also, we probably can do this more efficiently
|
||||
# / smarter by only redrawing the portion of the
|
||||
# path necessary?
|
||||
{
|
||||
60: hist_viz,
|
||||
1: viz,
|
||||
}[timeframe].update_graphics(
|
||||
force_redraw=True
|
||||
)
|
||||
# match msg:
|
||||
# case {
|
||||
# 'backfilling': (viz_name, timeframe),
|
||||
# } if (
|
||||
# viz_name == name
|
||||
# ):
|
||||
# log.warning(
|
||||
# f'Forcing HARD REDRAW:\n'
|
||||
# f'name: {name}\n'
|
||||
# f'timeframe: {timeframe}\n'
|
||||
# )
|
||||
# # TODO: only allow this when the data is IN VIEW!
|
||||
# # also, we probably can do this more efficiently
|
||||
# # / smarter by only redrawing the portion of the
|
||||
# # path necessary?
|
||||
# {
|
||||
# 60: hist_viz,
|
||||
# 1: viz,
|
||||
# }[timeframe].update_graphics(
|
||||
# force_redraw=True
|
||||
# )
|
||||
|
||||
# check if slow chart needs an x-domain shift and/or
|
||||
# y-range resize.
|
||||
|
@ -309,6 +310,7 @@ async def increment_history_view(
|
|||
|
||||
async def graphics_update_loop(
|
||||
|
||||
dss: dict[str, DisplayState],
|
||||
nurse: trio.Nursery,
|
||||
godwidget: GodWidget,
|
||||
feed: Feed,
|
||||
|
@ -350,8 +352,6 @@ async def graphics_update_loop(
|
|||
'i_last_slow_t': 0, # multiview-global slow (1m) step index
|
||||
}
|
||||
|
||||
dss: dict[str, DisplayState] = {}
|
||||
|
||||
for fqme, flume in feed.flumes.items():
|
||||
ohlcv = flume.rt_shm
|
||||
hist_ohlcv = flume.hist_shm
|
||||
|
@ -470,67 +470,68 @@ async def graphics_update_loop(
|
|||
if ds.hist_vars['i_last'] < ds.hist_vars['i_last_append']:
|
||||
await tractor.pause()
|
||||
|
||||
try:
|
||||
# XXX TODO: we need to do _dss UPDATE here so that when
|
||||
# a feed-view is switched you can still remote annotate the
|
||||
# prior view..
|
||||
from . import _remote_ctl
|
||||
_remote_ctl._dss = dss
|
||||
# try:
|
||||
|
||||
# main real-time quotes update loop
|
||||
stream: tractor.MsgStream
|
||||
async with feed.open_multi_stream() as stream:
|
||||
assert stream
|
||||
async for quotes in stream:
|
||||
quote_period = time.time() - last_quote_s
|
||||
quote_rate = round(
|
||||
1/quote_period, 1) if quote_period > 0 else float('inf')
|
||||
# XXX TODO: we need to do _dss UPDATE here so that when
|
||||
# a feed-view is switched you can still remote annotate the
|
||||
# prior view..
|
||||
from . import _remote_ctl
|
||||
_remote_ctl._dss.update(dss)
|
||||
|
||||
# main real-time quotes update loop
|
||||
stream: tractor.MsgStream
|
||||
async with feed.open_multi_stream() as stream:
|
||||
# assert stream
|
||||
async for quotes in stream:
|
||||
quote_period = time.time() - last_quote_s
|
||||
quote_rate = round(
|
||||
1/quote_period, 1) if quote_period > 0 else float('inf')
|
||||
if (
|
||||
quote_period <= 1/_quote_throttle_rate
|
||||
|
||||
# in the absolute worst case we shouldn't see more then
|
||||
# twice the expected throttle rate right!?
|
||||
# and quote_rate >= _quote_throttle_rate * 2
|
||||
and quote_rate >= display_rate
|
||||
):
|
||||
pass
|
||||
# log.warning(f'High quote rate {mkt.fqme}: {quote_rate}')
|
||||
|
||||
last_quote_s: float = time.time()
|
||||
|
||||
for fqme, quote in quotes.items():
|
||||
ds = dss[fqme]
|
||||
ds.quotes = quote
|
||||
rt_pi, hist_pi = pis[fqme]
|
||||
|
||||
# chart isn't active/shown so skip render cycle and
|
||||
# pause feed(s)
|
||||
if (
|
||||
quote_period <= 1/_quote_throttle_rate
|
||||
|
||||
# in the absolute worst case we shouldn't see more then
|
||||
# twice the expected throttle rate right!?
|
||||
# and quote_rate >= _quote_throttle_rate * 2
|
||||
and quote_rate >= display_rate
|
||||
fast_chart.linked.isHidden()
|
||||
or not rt_pi.isVisible()
|
||||
):
|
||||
pass
|
||||
# log.warning(f'High quote rate {mkt.fqme}: {quote_rate}')
|
||||
print(f'{fqme} skipping update for HIDDEN CHART')
|
||||
fast_chart.pause_all_feeds()
|
||||
continue
|
||||
|
||||
last_quote_s = time.time()
|
||||
ic = fast_chart.view._in_interact
|
||||
if ic:
|
||||
fast_chart.pause_all_feeds()
|
||||
print(f'{fqme} PAUSING DURING INTERACTION')
|
||||
await ic.wait()
|
||||
fast_chart.resume_all_feeds()
|
||||
|
||||
for fqme, quote in quotes.items():
|
||||
ds = dss[fqme]
|
||||
ds.quotes = quote
|
||||
rt_pi, hist_pi = pis[fqme]
|
||||
# sync call to update all graphics/UX components.
|
||||
graphics_update_cycle(
|
||||
ds,
|
||||
quote,
|
||||
)
|
||||
|
||||
# chart isn't active/shown so skip render cycle and
|
||||
# pause feed(s)
|
||||
if (
|
||||
fast_chart.linked.isHidden()
|
||||
or not rt_pi.isVisible()
|
||||
):
|
||||
print(f'{fqme} skipping update for HIDDEN CHART')
|
||||
fast_chart.pause_all_feeds()
|
||||
continue
|
||||
|
||||
ic = fast_chart.view._in_interact
|
||||
if ic:
|
||||
fast_chart.pause_all_feeds()
|
||||
print(f'{fqme} PAUSING DURING INTERACTION')
|
||||
await ic.wait()
|
||||
fast_chart.resume_all_feeds()
|
||||
|
||||
# sync call to update all graphics/UX components.
|
||||
graphics_update_cycle(
|
||||
ds,
|
||||
quote,
|
||||
)
|
||||
|
||||
finally:
|
||||
# XXX: cancel any remote annotation control ctxs
|
||||
_remote_ctl._dss = None
|
||||
for cid, (ctx, aids) in _remote_ctl._ctxs.items():
|
||||
await ctx.cancel()
|
||||
# finally:
|
||||
# # XXX: cancel any remote annotation control ctxs
|
||||
# _remote_ctl._dss = None
|
||||
# for cid, (ctx, aids) in _remote_ctl._ctxs.items():
|
||||
# await ctx.cancel()
|
||||
|
||||
|
||||
def graphics_update_cycle(
|
||||
|
@ -1554,8 +1555,10 @@ async def display_symbol_data(
|
|||
)
|
||||
|
||||
# start update loop task
|
||||
dss: dict[str, DisplayState] = {}
|
||||
ln.start_soon(
|
||||
graphics_update_loop,
|
||||
dss,
|
||||
ln,
|
||||
godwidget,
|
||||
feed,
|
||||
|
@ -1569,15 +1572,31 @@ async def display_symbol_data(
|
|||
order_ctl_fqme: str = fqmes[0]
|
||||
mode: OrderMode
|
||||
async with (
|
||||
|
||||
open_order_mode(
|
||||
feed,
|
||||
godwidget,
|
||||
order_ctl_fqme,
|
||||
order_mode_started,
|
||||
loglevel=loglevel
|
||||
) as mode
|
||||
):
|
||||
) as mode,
|
||||
|
||||
# TODO: maybe have these startup sooner before
|
||||
# order mode fully boots? but we gotta,
|
||||
# -[ ] decouple the order mode bindings until
|
||||
# the mode has fully booted..
|
||||
# -[ ] maybe do an Event to sync?
|
||||
|
||||
# start input handling for ``ChartView`` input
|
||||
# (i.e. kb + mouse handling loops)
|
||||
rt_chart.view.open_async_input_handler(
|
||||
dss=dss,
|
||||
),
|
||||
hist_chart.view.open_async_input_handler(
|
||||
dss=dss,
|
||||
),
|
||||
|
||||
):
|
||||
rt_linked.mode = mode
|
||||
|
||||
rt_viz = rt_chart.get_viz(order_ctl_fqme)
|
||||
|
|
|
@ -201,8 +201,8 @@ async def open_signal_handler(
|
|||
async for args in recv:
|
||||
await async_handler(*args)
|
||||
|
||||
async with trio.open_nursery() as n:
|
||||
n.start_soon(proxy_to_handler)
|
||||
async with trio.open_nursery() as tn:
|
||||
tn.start_soon(proxy_to_handler)
|
||||
async with send:
|
||||
yield
|
||||
|
||||
|
@ -212,18 +212,48 @@ async def open_handlers(
|
|||
|
||||
source_widgets: list[QWidget],
|
||||
event_types: set[QEvent],
|
||||
async_handler: Callable[[QWidget, trio.abc.ReceiveChannel], None],
|
||||
**kwargs,
|
||||
|
||||
# NOTE: if you want to bind in additional kwargs to the handler
|
||||
# pass in a `partial()` instead!
|
||||
async_handler: Callable[
|
||||
[QWidget, trio.abc.ReceiveChannel], # required handler args
|
||||
None
|
||||
],
|
||||
|
||||
# XXX: these are ONLY inputs available to the
|
||||
# `open_event_stream()` event-relay to mem-chan factor above!
|
||||
**open_ev_stream_kwargs,
|
||||
|
||||
) -> None:
|
||||
'''
|
||||
Connect and schedule an async handler function to receive an
|
||||
arbitrary `QWidget`'s events with kb/mouse msgs repacked into
|
||||
structs (see above) and shuttled over a mem-chan to the input
|
||||
`async_handler` to allow interaction-IO processing from
|
||||
a `trio` func-as-task.
|
||||
|
||||
'''
|
||||
widget: QWidget
|
||||
streams: list[trio.abc.ReceiveChannel]
|
||||
async with (
|
||||
trio.open_nursery() as n,
|
||||
trio.open_nursery() as tn,
|
||||
gather_contexts([
|
||||
open_event_stream(widget, event_types, **kwargs)
|
||||
open_event_stream(
|
||||
widget,
|
||||
event_types,
|
||||
**open_ev_stream_kwargs,
|
||||
)
|
||||
for widget in source_widgets
|
||||
]) as streams,
|
||||
):
|
||||
for widget, event_recv_stream in zip(source_widgets, streams):
|
||||
n.start_soon(async_handler, widget, event_recv_stream)
|
||||
for widget, event_recv_stream in zip(
|
||||
source_widgets,
|
||||
streams,
|
||||
):
|
||||
tn.start_soon(
|
||||
async_handler,
|
||||
widget,
|
||||
event_recv_stream,
|
||||
)
|
||||
|
||||
yield
|
||||
|
|
|
@ -23,6 +23,7 @@ from contextlib import (
|
|||
asynccontextmanager,
|
||||
ExitStack,
|
||||
)
|
||||
from functools import partial
|
||||
import time
|
||||
from typing import (
|
||||
Callable,
|
||||
|
@ -74,6 +75,7 @@ if TYPE_CHECKING:
|
|||
)
|
||||
from ._dataviz import Viz
|
||||
from .order_mode import OrderMode
|
||||
from ._display import DisplayState
|
||||
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
@ -102,6 +104,7 @@ async def handle_viewmode_kb_inputs(
|
|||
|
||||
view: ChartView,
|
||||
recv_chan: trio.abc.ReceiveChannel,
|
||||
dss: dict[str, DisplayState],
|
||||
|
||||
) -> None:
|
||||
|
||||
|
@ -177,17 +180,42 @@ async def handle_viewmode_kb_inputs(
|
|||
Qt.Key_P,
|
||||
}
|
||||
):
|
||||
import tractor
|
||||
feed = order_mode.feed # noqa
|
||||
chart = order_mode.chart # noqa
|
||||
viz = chart.main_viz # noqa
|
||||
vlm_chart = chart.linked.subplots['volume'] # noqa
|
||||
vlm_viz = vlm_chart.main_viz # noqa
|
||||
dvlm_pi = vlm_chart._vizs['dolla_vlm'].plot # noqa
|
||||
import tractor
|
||||
await tractor.pause()
|
||||
view.interact_graphics_cycle()
|
||||
|
||||
# SEARCH MODE #
|
||||
# FORCE graphics reset-and-render of all currently
|
||||
# shown data `Viz`s for the current chart app.
|
||||
if (
|
||||
ctrl
|
||||
and key in {
|
||||
Qt.Key_R,
|
||||
}
|
||||
):
|
||||
fqme: str
|
||||
ds: DisplayState
|
||||
for fqme, ds in dss.items():
|
||||
|
||||
viz: Viz
|
||||
for tf, viz in {
|
||||
60: ds.hist_viz,
|
||||
1: ds.viz,
|
||||
}.items():
|
||||
# TODO: only allow this when the data is IN VIEW!
|
||||
# also, we probably can do this more efficiently
|
||||
# / smarter by only redrawing the portion of the
|
||||
# path necessary?
|
||||
viz.reset_graphics()
|
||||
|
||||
# ------ - ------
|
||||
# SEARCH MODE
|
||||
# ------ - ------
|
||||
# ctlr-<space>/<l> for "lookup", "search" -> open search tree
|
||||
if (
|
||||
ctrl
|
||||
|
@ -247,8 +275,10 @@ async def handle_viewmode_kb_inputs(
|
|||
delta=-view.def_delta,
|
||||
)
|
||||
|
||||
elif key == Qt.Key_R:
|
||||
|
||||
elif (
|
||||
not ctrl
|
||||
and key == Qt.Key_R
|
||||
):
|
||||
# NOTE: seems that if we don't yield a Qt render
|
||||
# cycle then the m4 downsampled curves will show here
|
||||
# without another reset..
|
||||
|
@ -431,6 +461,7 @@ async def handle_viewmode_mouse(
|
|||
|
||||
view: ChartView,
|
||||
recv_chan: trio.abc.ReceiveChannel,
|
||||
dss: dict[str, DisplayState],
|
||||
|
||||
) -> None:
|
||||
|
||||
|
@ -567,6 +598,7 @@ class ChartView(ViewBox):
|
|||
@asynccontextmanager
|
||||
async def open_async_input_handler(
|
||||
self,
|
||||
**handler_kwargs,
|
||||
|
||||
) -> ChartView:
|
||||
|
||||
|
@ -577,14 +609,20 @@ class ChartView(ViewBox):
|
|||
QEvent.KeyPress,
|
||||
QEvent.KeyRelease,
|
||||
},
|
||||
async_handler=handle_viewmode_kb_inputs,
|
||||
async_handler=partial(
|
||||
handle_viewmode_kb_inputs,
|
||||
**handler_kwargs,
|
||||
),
|
||||
),
|
||||
_event.open_handlers(
|
||||
[self],
|
||||
event_types={
|
||||
gs_mouse.GraphicsSceneMousePress,
|
||||
},
|
||||
async_handler=handle_viewmode_mouse,
|
||||
async_handler=partial(
|
||||
handle_viewmode_mouse,
|
||||
**handler_kwargs,
|
||||
),
|
||||
),
|
||||
):
|
||||
yield self
|
||||
|
|
|
@ -930,13 +930,8 @@ async def open_order_mode(
|
|||
msg,
|
||||
)
|
||||
|
||||
# start async input handling for chart's view
|
||||
async with (
|
||||
|
||||
# ``ChartView`` input async handler startup
|
||||
chart.view.open_async_input_handler(),
|
||||
hist_chart.view.open_async_input_handler(),
|
||||
|
||||
# pp pane kb inputs
|
||||
open_form_input_handling(
|
||||
form,
|
||||
|
|
Loading…
Reference in New Issue