Skip to content
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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Conversation

ronpandolfi
Copy link
Contributor

No description provided.

@teddyrendahl teddyrendahl mentioned this pull request May 31, 2019
@ronpandolfi ronpandolfi requested a review from teddyrendahl July 13, 2019 00:00
@ronpandolfi
Copy link
Contributor Author

@teddyrendahl How does this merge look?

@@ -0,0 +1,305 @@
import logging
from bluesky import RunEngine
from bluesky.utils import RunEngineInterrupted, install_qt_kicker
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


class QRunEngine(QObject):
sigDocumentYield = Signal(str, dict)
sigAbort = Signal() # TODO: wireup me
Copy link
Collaborator

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}')
Copy link
Collaborator

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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.

Copy link
Collaborator

@teddyrendahl teddyrendahl left a 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}')
Copy link
Collaborator

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
Copy link
Collaborator

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.

pcdshub/typhos#48 (comment)

Copy link
Member

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
Copy link
Collaborator

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):
Copy link
Collaborator

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

Copy link
Member

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:
Copy link
Collaborator

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
Copy link
Collaborator

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.

@tacaswell
Copy link
Member

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)
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants