Starting with the recently-introduced Mesh Painters concept, K-3D developers are now encouraged to create special-purpose plugins to support hardware-specific and/or API-specific functionality. For example, the OpenGLFacePainter plugin is written to render mesh faces using lowest-common-denominator OpenGL calls, while the OpenGLVBOFacePainter plugin uses the OpenGL vertex buffer extension for vastly improved render performance, on those platform/hardware combinations that support the extension. Other examples of this type of API-specific plugin include the GnomeURIHandler / OSXURIHandler / WindowsURIHandler plugins, which use platform-specific APIs to display a URI in the user's choice of web-browser, and the GLXCameraToBitmap / WGLCameraToBitmap plugins, which can perform offscreen rendering on X and Win32, respectively.
This approach to development is flexible, efficient, and leads to small, well-defined plugin "components". However, it introduces some additional complexity:
- Ideally, the choice between two plugins such as OpenGLFacePainter and OpenGLVBOFacePainter should be made automatically at runtime without user intervention.
- On the other hand, developers need to have fine-grained control over the instantiation of plugins during development and testing. "Power" users should also be able to explicitly instantiate objects based on their particular needs.
- At the same time, we need to ensure that documents created by casual users on one platform will be usable with other platforms of differing capabilities.
Assume the following: user "A" has recent hardware that supports the OpenGL vertex buffer extension. User "B" does not. User A creates a new document, and in the process an instance of OpenGLVBOFacePainter is automatically created. If user A saves the document and shares it with user B, the document will not work properly, because user B's hardware does not support OpenGLVBOFacePainter. Conversely, if user B creates a similar document, and instance of OpenGLFacePainter is automatically created. When the document is shared with user A, the OpenGLFacePainter instance will work correctly, but will not take full advantage of user A's hardware.
In the past there has been a one-to-one correspondance between plugin factories and plugin types - for each type of plugin available in K-3D, there was exactly one plugin factory that was responsible for creating instances of that plugin type. To address the issue of instantiating platform-specific plugins while still maintaining a high level of control for developers and power users, we have introduced "virtual" plugins. In particular, we've relaxed the requirement that a plugin factory always produce instances of the same C++ class. This allows us to create special factories whose sole purpose is to instantiate different plugin types at runtime based on whatever logic is appropriate (such as querying the user's hardware).
Continuing the above example, we might create a new plugin factory for a "virtual" plugin called VirtualOpenGLFacePainter. This plugin would have its own unique class ID and name, but instead of being hard-coded to always produce instances of a specific C++ plugin class, VirtualOpenGLFacePainter inspects the user's hardware and creates instances of OpenGLFacePainter or OpenGLVBOFacePainter accordingly. Because a serialized document contains an instance of VirtualOpenGLFacePainter rather than an instance of either of the concrete face painter types, the VirtualOpenGLFacePainter factory can create instances of the "correct" type at runtime, no matter the platform used to open a document. If a developer or power user wants to explicitly create either type of plugin, they can continue to do so in the usual way - both the virtual plugin and the concrete plugins will appear in the user interface and work in the expected way.
The main code change to make this possible is the addition to every node of a reference to the factory that created it - when queried via the k3d::inode::factory() method, each node returns this stored factory reference. When the user instantiates a virtual plugin, the virtual plugin factory delegates construction of the concrete plugin to the concrete plugin's factory, but passes itself to the new node as the "creating" factory. Thus, the new node "appears to be" an instance of the virtual plugin, rather than an instance of the concrete plugin.