Plugin system

From OpenRocket wiki
Jump to navigation Jump to search

The Problem

There are many cases where it is desirable to add a feature to openrocket which is useful in a specific context, but not really appropriate for inclusion in openrocket core. For this reason it is desirable to have some kind of plugin framework, as is found in many other applications.

In our case we refer to plugins as something that is installed onto the user's computer which is found by OR and provides additional functionality. The functionality might be simulation listeners, new motor types etc.

The plugin system has ties to dependency injection, and ideally one solution would cover both plugin discovery related items and dependency injection.

Plugin Directory

OpenRocket plugins can be installed to the following folder:

Windows: C:\Users\XXX\AppData\Roaming\OpenRocket\Plugins\

Linux: ~/.openrocket/Plugins/

Mac: Root/Applications/OpenRocket/PlugIns

Type of plugins

A list of different use-cases for plugins which have different kinds of requirements:

  • Simulation listeners: Tied to a specific simulation, possible to select from a menu and configurable via a UI. Configuration needs to be stored/loaded from the ORK file.
  • Auxilliary tools: Plugin adds options to some menu on the main window. Plugin may interact or modify the rocket. (Similar to rocket optimization)
  • Optimization modifiers/targets: Defining new types of optimization targets or parameters.
  • Startup tips: New startup tip types. (Not implemented yet)
  • New motor types
  • New simulation / aerodynamic computation engines

Some of these may be singletons, others contain state and need to be instantiated properly.


Requirements

  • Configuration
    • Some plugins need configuration via a UI
    • How to distinguish what is part of core and what is a plugin?
  • Automatic discovery
    • Drop a JAR to the plugin folder and it will be used.
  • Localisation support
  • Dependency management
    • Some plugins may depend on others and be able to automatically load them. This in turn requires that plugins have a consistent identifier / URI and version number.
    • I don't think we need plugin dependency management in the foreseeable future, and such a system may complicate things considerably. -Sampo
  • Plugins implemented in other languages
    • Should be possible to create a plugin that allows writing other plugins in scripting languages (e.g. JavaScript/Ruby)
  • Simplified access to openrocket document / software state
    • E.g. currently selected component
  • Compatibility
    • What happens when changes are made to the core that break the plugin?
    • Should we have some API versioning that defines which plugins are compatible?


Anti requirements

(Discuss)

Implementation

Options for discussion:

JSPF

http://code.google.com/p/jspf/

Pro:

  • Has direct functionality for loading all JAR files from a certain directory as plugins
  • Straightforward to use
  • Can auto-inject other plugins as dependencies
  • Annotation-based plugin definition
  • Light-weight (JSPF core is 320K)
  • Works on Android (though automatic discovery is not possible)

Cons:

  • Plugin-orientated, not directly meant for DI (though can be used for that somewhat)
  • Plugins are always instantiated by JSPF, you cannot register a plugin instance. Thus massive use of provider interfaces would be required.
  • All plugins are singletons. Need to use provider interfaces.

(Provider interface means that you do not directly ask for a plugin, but instead ask for a plugin provider, which in turn returns 0-N plugin instances.)


ServiceLoader + home made

http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html

Pro:

  • Simple, built into the JRE
  • Already used in some parts of OR
  • Works on Android

Con:

  • JARs from plugin directory need to be added to classpath separately (not a bit thing with a custom ClassLoader)
  • Does not do DI
  • We need to write framework code for plugin management
  • Plugin writers need to define the proper service META-INF (no annotations)


Reflections + home made

https://code.google.com/p/reflections/

Pro:

  • Allows easily to search the classpath for annotations or interface implementations
  • Full flexibility on plugin structure

Cons:

  • JARs from plugin directory need to be added to classpath separately (not a bit thing with a custom ClassLoader)
  • Does not do DI
  • Unknown does it work on Android
  • The core is lightweight, but requires (at least) Guava and Javassist, altogether ~2MB


Spring

http://www.springsource.org/

Pro:

  • Proper IoC framework, built for the purpose
  • Supports AutoWiring of dependencies
  • Should be possible to list all beans matching to an interface/annotation
  • Modular, we need core+beans (~1MB)

Cons:

  • JARs from plugin directory need to be added to classpath separately (not a bit thing with a custom ClassLoader)
  • Unknown does it work on Android
  • Need some boilerplate code for pluging functionality
  • Might need provider interfaces (or some black magic using BeanFactories etc)


Guice

http://code.google.com/p/google-guice/

Pro:

Cons:

  • Unfamiliar to Sampo :)

Others

Other options that have been considered but seem too immature or too heavy to use:

  • JPF (http://jpf.sourceforge.net/), seems more customizable and configurable than JSPF, but not sure do we need the extra muscle. Looks functional, but last activity was in 2007.
  • OSGi (http://en.wikipedia.org/wiki/OSGi), even more heavy-weight service discovery mechanism, similar to (or used by) Eclipse plug-in system.
  • Java Module System (JSR 277), slated for inclusion to Java 7, but wasn't. Seems pretty inactive (early draft from 2006) and seems to include language changes.


A Rough Interface Design (from a plugin writers point of view)

This section considers plugins from the point of view of "auxilliary tool" plugins, which is a minority use case. Take this section with a grain of salt.

General operation:

The name for each plugin will correspond to an item in the plugins menu. Subitems of each item in the plugins menu will be automatically generated as follows:

  • About plugin : makes an about box, present for all plugins.
  • for each 'enableable' modifier, a check box of the form [X] Enable ModifierName will be generated. When enabled, the enable method of the modifier will be called. This would then do what is necessary to modify OR to always run the plugin code, for example: make the simulation always load the simulation modifier.
  • for each 'configurable' modifier, an item which opens the configuration window
  • any additional menu items as defined in each modifier. For example, a rocket modifier might have an item which changes the selected component in some way.

Code design:

interface Plugin:
 - name, version, URI, date, author, licence, description
 - list of dependent plugin URI's and versions
 - list of conflicting plugin URI's and versions
 - list of modifiers
abstract class AbstractPlugin implements Plugin:
 * everything required for loading, setting menu, translations etc
 - getSetting(key), setSetting(key, value) : a simple mechanism for locally storing various settings
 - getFile(name), storeFile(file), rmfile(name) : a simple mechanism for locally storing cache files etc
static class MyPlugin extends AbstractPlugin:
 * the users implementation
files:
 messages_?.properties : translations to use, inside plugin jar
interface Modifier:
 - plugin this modifier is a member of
 - enableable boolean
 - enable(), disable()
 - configurable boolean
 - configurator
 - additional menu items and actions
abstract class RocketModifier implements Modifier:
 - enable(), disable() : 
 - getSelectedComponent() : returns currently selected rocket component
 - onChange() : called when a component is changed
 * various other helper methods as necessary
static class MyRocketModifier:
 * the users implementation of something that works with or modifies the rocket design
abstract class SimulationModifier implements Modifier:
 * basically everything currently in simulation listeners, possibly simplified (do we need 3 separate interfaces?)
static class MySimulationModifier extends AbstractSimulationModifier:
 * the users implementation
abstract class configurator:
 a class which automatically generates a standardised configuration dialog box based on just a simple list of variables and selectors.
 Variables would be saved automatically when the OK button is clicked using plugin setSettings().