-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QObject based RunEngine #9
base: master
Are you sure you want to change the base?
Conversation
@teddyrendahl How does this merge look? |
@@ -0,0 +1,305 @@ | |||
import logging | |||
from bluesky import RunEngine | |||
from bluesky.utils import RunEngineInterrupted, install_qt_kicker |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
install_qt_kicker
deprecated: https://github.com/bluesky/bluesky/blob/f18f759e58bfa2453c66c524d0479d409f64304c/bluesky/utils.py#L785
|
||
class QRunEngine(QObject): | ||
sigDocumentYield = Signal(str, dict) | ||
sigAbort = Signal() # TODO: wireup me |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet wired up?
# Instantiate widget information and layout | ||
super().__init__(parent=parent) | ||
|
||
self.setStyleSheet('QLabel {qproperty-alignment: AlignCenter}') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if mily should make a consistent set of rules as to how QWidget
subclasses handle style sheets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily disagreeing but I think this is pretty harmless here? I believe this is to avoid running a for
loop over all the labels to and using QLabel.setAlignment
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harmless, sure - but not so easily extensible.
I doubt it's a controversial opinion, but I think everything in mily should be designed with extensibility in mind, where possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this @ronpandolfi!
# Instantiate widget information and layout | ||
super().__init__(parent=parent) | ||
|
||
self.setStyleSheet('QLabel {qproperty-alignment: AlignCenter}') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily disagreeing but I think this is pretty harmless here? I believe this is to avoid running a for
loop over all the labels to and using QLabel.setAlignment
.
self.engine = engine or QRunEngine() | ||
|
||
|
||
@property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the first iteration @tacaswell suggested that we instead have a start
QSlot
that accepts a plan. That way you can easily pass in plans w/o dealing with attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still stand by that suggestion, the RE widget should not hold any plans as state, but should be passed plans to be run by some other part of the GUI.
Not sure if passing (plan_fun, args, kwargs)
and expecting this widget to call the plan or passing in an iterable makes more sense. In the case of passing in the callable + args it gives the widget a chance to do some nice introspcetion (like displaying the name of the plan it is running). On the other hand, the iterable scheme matches what the RE
does more directly.
self.setLayout(lay) | ||
self._plan = None | ||
|
||
# Accept either RunEngine or QRunengine for engine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this logic needs to go in the engine.setter
if we truly want to accept both? For example:
# Will be QRunEngine
RE = RunEngine()
ew = EngineWidget(engine=RE)
# Won't be QRunEngine
RE = RunEngine()
ew = EngineWidget()
ew.engine = RE
return self._engine | ||
|
||
@engine.setter | ||
def engine(self, engine): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we have an existing _engine
we need to disconnect the prior signals from their slots. Otherwise we risk having our widget being updated by multiple engines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear to me that allowing changing the runengine behind the widget is something we want to allow. If we really need to throw away a RunEngine
instance might as well make a new Qt widget for it as well?
try: | ||
func(self.engine) | ||
# Pausing raises an exception | ||
except RunEngineInterrupted as exc: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth catching generic Exception
objects here. Nobody likes a GUI crash and this is where any bubbles from the execution of the plan will bubble up. There might also need to be some logic to make sure it is ready to take another plan afterwards (teardown and re-up of a failed RunEngine execution)? 🤔
|
||
from mily.runengine import EngineWidget | ||
|
||
# Fixtures borrowed from pytest-qt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just add pytest-qt
as you discussed offline. Commenting here as a reminder.
Can someone remind me what the plan for this and https://github.com/bluesky/bluesky-ui/tree/master/bluesky_ui is? |
def __init__(self, runengine=None, **kwargs): | ||
super(QRunEngine, self).__init__() | ||
|
||
self.RE = runengine or RunEngine(context_managers=[], **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that we need to create the RE with just the right settings (no context managers, probably a different during_task
, and maybe using qthreads instead of python threads to push the asyncio event loop to the background, maybe with qumash to embed the asyncio event loop in the qt event loop (!!)) I don't think we should allow the user to pass in an existing RunEngine
No description provided.