Compile-time vs Runtime Manager descriptions

I recently started a branch that provides a uniform API for plugins to register things to different SMTK managers (currently resource::Manager and operation::Manager, but more to come!). The pattern I used requires the managers to be known at compile-time: a manager that has things register to it cannot be introduced to SMTK via a runtime-loadable plugin. It does not mean that all SMTK managers must be present at compile-time, though: a manager can still be disabled via a compile-time switch (much like the Qt or VTK extensions), and can be linked into a project via plugins that explicitly call it (if the plugin isn’t loaded, neither is the manager).

By requiring that managers are known at compile-time, we gain the flexibility of adding to and removing from these managers via plugins without worrying worrying about plugin load order issues and without incurring the penalty of a double-dispatch between managers and plugins (see smtk::common::Registry for more details). Pinning down one side of the registration process simplifies the logic of registration a great deal; in this setup, we do not have to worry about a manager being introduced to a system after a plugin that would register to it, for example. A compile-time implementation also avoids the overuse of polymorphism, reducing the intellectual buy-in for creating an SMTK plugin.

As a concrete use-case, we have the smtk::bridge::polygon session (a plugin) that registers to different managers. If you wish to create a new smtk::foo manager and have the polygon session register to it, you could create smtk::foo::Manager and its logic in and either

  • extend (i.e. modify) the polygon session’s registration logic to include it, or

  • create a new plugin that brings in both smtk::foo::Manager, the polygon session and the registration logic between the two.

In both scenarios, new code that uses both the polygon session and the new manager’s feature set would need to be written, so a link between and will have to exist in any pattern. Determining where and how this link is made is simply bookkeeping.

One alternative to compile-time manager descriptions would be to connect managers via an abstract base class smtk::common::Manager and “managed” things via an abstract base class smtk::common::Managed. We would then need to construct an extensible and plugin-linktime executable set of logic for performing a double-dispatch over managers and managed elements. While performance isn’t really an issue for events that only occur at plugin load/unload, double-dispatch solutions are rarely elegant, and this approach would require an increased buy-in for plugin developers (and brings us closer to a vtkObject-style use of polymorphism).

I am heavily biased towards compile-time solutions when I think they are appropriate, so take this all with a grain of salt. Please let me know what you think!

This is the crux of my objection: the application must manage the cross product of all the managers and registrars, with foreknowledge of both. It would be impossible to have a FooManagerPlugin and a PolygonSessionPlugin that, when loaded independently, would register the polygon session with the foo manager without complete compile+link-time interdependency; the manager plugin would have to explicitly depend on (and thus enable upon loading) all of the sessions it could manage or we would need to have a FooPolygonSessionPlugin, a FooMeshSessionPlugin, etc. for every combination we want. That sounds like a lot of work and a fragile system.

The third option is the one that we should follow:

As I said above:

There is no such thing as a free lunch. Some compiled code is going to have to know about both libraries in order for it to be useful.

I don’t fully understand this topic, but my feeling is that it is reasonable for plugin authors to know which managers to register their code with (maybe even preferable?), so I am inclined to prefer the compile-time design.

Yes, the polygon session must know what to do with such a manager. However, we may wish for that manager to be optional in the application. For example, if we use managers for asynchronous operation launchers, there may be customers for whom we do not wish to provide asynchronous operations. But with TJ’s branch, all of the session plugins that can make use of the launcher manager must link to the thing which provides the instance of the launch manager.

It is this dependency I object to and which the compile-time-only registrar requires. The application should be able to treat managers as black boxes that it offers to plugins that request them.

I don’t want a free lunch. I just don’t want your lunch. :slight_smile:

Joke’s on you. We had wings today.

I don’t see any compelling use-cases for optional managers that can’t be dealt with using compile-time switches. To date, SMTK 3.0 will have

  • Resource manager
  • Operation manager
  • Workflow manager
  • UI Manager
  • Selection manager

The current plan is to have all of these managers in SMTK core, so the point there is moot.

How do you propose creating a plugin that a) avoids linking against a library, b) also has an API that uses that library and c) is not granular (i.e. the section of your plugin that uses the API is not in its own library)? If your concern is polluting the space of linked libraries, I don’t think your solution solves this problem either. If your concern is the registration of these pieces occurring when you don’t need/want them, then compile-time registration is not a dealbreaker (the code is assembled at compile-time, but is still executed during run- or static-time).

I’m not sure I understand. Core does not provide instances of these managers. That is the whole reason smtkEnvironment exists.

I do not. The plugin will link to smtkCore (and whatever libraries define the different managers). However, the plugin will not link to the library that creates+owns instance(s) of those managers.

My problem is that I do not see how your design as it is can accommodate the owner of an instance of a manager being present or absent.

If you are concerned about compile-time dependencies, then instances of these managers do not need to exist in order for the dependencies to occur. It suffices to have a function that accepts one of these managers as an argument (like for registration).

Ok, now we are getting somewhere!

If we ignore the environment namespaces for a moment, then there are no instances of any managers present in SMTK (since there are no global statics). All SMTK provides is the ability to create managers and the ability to register to them (wherever they are). It is the responsibility of the consuming project (e.g. ModelBuilder) to use these tools to do things.

To that end, smtk::common::Registry instances share a common base so that the consuming project can hold on to a slew of these things, and push/pop them as the libraries come and go. ModelBuilder needs to create these managers and register/unregister the plugins that go with them. If you create an application that doesn’t use operation managers, that’s cool. You’ll still need to link against it (there is code compiled that refers to the operation manager), but if you don’t create an instance of the operation manager and if your application’s registry template class instantiation doesn’t mention the operation manager, then no operation registration will occur.

I am concerned about compile time dependencies which force every pair of instances (manager instance and thing-which-registers-to-manager) to be its own plugin.

When we talked about this last week, you said there would be problems if managers were not all created at the time that the registry instances were created. I think it will be difficult to force all the managers to be present before any registry instances are created (imagine more than 1 plugin providing both).

Yup. A registry takes an instance of the manager to which it registers a plugin.

Again, it is the responsibility of the consuming project to create the manager. It is also the responsibility of the consuming project to register a plugin to the manager by creating a registry. Why will it be difficult for a consuming project to provide the manager instance to the registry instance, if both are created by the consuming project?