-
Notifications
You must be signed in to change notification settings - Fork 112
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
add callbacks for device (un)loading #163
base: main
Are you sure you want to change the base?
Conversation
virtual void onAllDevicesWillBeUnloaded() | ||
{ | ||
std::cout << "onAllDevicesWillBeUnloaded "; | ||
} |
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.
Ideally I think this would be made irrelevant by the single device unloading signal. But that doesn't look like it's possible without pushing the external callback into the deviceManager which currently doesn't happen. Happy to do that, but figured Id start with the less invasive option.
fc2831c
to
aeb5066
Compare
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.
One problem is that devices are not available for interaction until they are initialized. Would it make more sense to have onDeviceDidBecomeAvailable
and onDeviceWillBecomeUnavailable
, and issue the former when initialization succeeds (and, technically, the latter before shutdown)? Otherwise, I'm not sure how an application could make use of these events.
If you want to react to actual (uninitialized) loading of devices (useful iff you want to modify pre-init properties), there could be a third event. The only use case I can think of is an implementation of a hardware configuration GUI, but even then, the GUI (being the one calling loadDevice()
) probably doesn't need such an event from the Core.
Also, the events need to be generated on code paths that are guaranteed to be taken when devices are initialized/shut down. This probably means in DeviceManager
.
I agree that there should only be events issued for each device individually. There is no need for an "unload all devices" event, and clients listening to these new events should ignore the (now redundant) config loaded event.
Thinking about this more (xref to #164) is it even ok to just add callbacks like this? I think with how the callback system is set up adding any new callback is an immediate backwards compatibility break. If you subclass MMEventCallback then downstream users are safe, but if not and you register a completely custom callback then when these get called they will result in errors.
That sounds reasonable. What's the difference between loaded and initialized? I naively assumed loaded meant ready for use.
Sounds good. Is the way to do that to pass a pointer to the externalCallback into the manager constructor here? mmCoreAndDevices/MMCore/MMCore.cpp Line 143 in ab2fc60
|
Devices cannot trigger their own unloading currently, other than by failing to initialize. If we add such a feature in the future (e.g. to react to somebody unplugging the USB cable -- some scopes can notify us of this), we can deal with it then. But it is unlikely, because unexpected unloading would break all existing code, such as MMStudio. Although unloading itself (the device object's destructor) never "fails", it is possible for the device's |
I don't think so -- For pymmcore there is no worry, because the mmCoreAndDevices submodule in that repo is not automatically updated. We'll just release a new version of pymmcore when the time comes. We don't really guarantee anything about backward binary compatibility of MMCore as a C++ library (which we don't officially support building). Please see the comment in the code regarding versioning. mmCoreAndDevices/MMCore/MMCore.cpp Line 82 in ab2fc60
The MMCore minor version should be bumped in this PR assuming I'm right. (Some day we'll need separate version numbers for MMCore vs MMCoreJ vs pymmcore.) EDIT: To clarify, I don't think we need to support backward compatibility for the unusual use case of directly instantiating the base |
The device objects use a two-step initialization process. The first step is the call to the global function The second step is to call the device object's Between the two steps, the app can modify the values of the device's "pre-initialization properties". Regular properties can only (correctly) be accessed after When tearing down, first |
Yes, but it would be a violation of separation of concerns for the The easiest thing to do would be to commit the even greater architectural crime of passing the pointer to |
Ah I missed the type requirements of register callback. I mistakenly thought you could avoid subclassing.
ah missed that! I'll bump the minor version. Thanks for the description of initialized vs loaded! At some point we should gather up all your comments on these PRs and put them into the docs. They have been extremely helpful and it would great to centralize them. |
So MMCore should be a callback object that implements something like:
and declare the DeviceManager as a also thanks for all your c++ help. I'm definitely learning as I go here and I really appreciate the guidance. That said I know that teaching c++ is probably not what you signed up for, so I'll try to spend some time reading up on c++ (any recommendations?) |
Good to hear. I'm definitely writing partially with that intent. Updating (or replacing) the how-to-write-device-adapters docs is something I've never gotten around to since starting working on Micro-Manager. It's daunting to me due to all the corner cases, but easier to write single-topic paragraphs like this. |
Don't let the perfect be the enemy of the good! Maybe we should create an issue on the docs repo and use it as a place to link to all these comments so that we don't lose track of them? |
The // In DeviceManager.h
struct DeviceManagerCallback
{
boost::function<std::string> deviceDidBecomeAvailableFunc;
boost::function<std::string> deviceWillBecomeUnavailableFunc;
}; // Called from CMMCore constructor and return value passed to DeviceManager constructor
private DeviceManagerCallback CMMCore::makeDeviceManagerCallback()
{
DeviceManagerCallback callbacks;
// Use a lambda expression to set the functions:
callbacks.deviceDidBecomeAvailableFunc = [this](const std::string& deviceName) {
onDeviceDidBecomeAvailable(deviceName); // Or write the code here to call externalCallback_
};
// Likewise for deviceWillBecomeUnavailableFunc
return callbacks;
} ( I would also be okay with a
It's been a pleasure, since you ask specific questions about concrete code! It's been a long time since I first learned C++ (mid-to-late '90s), so I don't know what is a good book these days. There is the added challenge that you should be learning modern C++ (C++11 and later), but much of the existing MM code is written in '80s or '90s style C++. I always look things up on cppreference.com. The "Back to the Basics" videos on the CppCon Youtube channel are excellent, though some of them might be more in-depth than you need. |
See also #164 (comment) regarding how these callbacks may require different concurrency considerations from existing ones. |
Closes: #149
Couldn't test locally due to my struggles with pymmcore described here: micro-manager/pymmcore#62tested locally, seems to be work as expected.I have two main worries:
deviceWillBeUnloaded
callback and then device unload fails