Designing the Module Manager

Architecture of PluginPlay calls for a module manager component which will be the user-facing API of PluginPlay. This section describes the design of that component.

What is the module manager?

Based on object-oriented design principles we need an object to represent the PluginPlay framework. The majority of interactions with the framework can be summarized as “module management” so we have opted to call the corresponding component the “module manager” and the literal class ModuleManager.

Module Manager Considerations

Since the module manager is the user-facing API every major feature of PluginPlay needs to be accessible through it. This means the module manager component needs to support:

  1. Dynamic module management

  • Users may hard-coded some configuration, but ultimately users also need to be able to override those configurations at runtime.

  • The state of the framework needs to adapt to changes in real time

  • The state of the framework needs to be query-able in real time.

  • Responsible for lifetime of modules (and framework)

  1. Managing of Module pool

    • Need to be able to load, remove, etc. Module objects

    • Iterate over, list, count, etc. loaded Module objects

    • Discern among different configurations

  2. Modify call graph

  3. Run call graph

    • Once the call graph is setup need to actually be able to run it.

    • Running call graph should be efficient/performant

  4. Saving, loading, and restarting a previous session

    • Intermediates can be expensive, need to manage size of save state

    • May need to go from distributed state to single node, vice versa

Module Manager Architecture

../../_images/mm_arch.png

Fig. 4 Architecture of the module manager component.

Fig. 4 shows the architecture of the module manager component. The user-facing API is codified by a ModuleManager class. Using the PIMPL idiom, the implementation of the ModuleManager (and PluginPlay itself) is separated from the API. The current design actually has the call graph, cache, and ParallelZone components residing within the ModuleManagerPIMPL (not as separate from the ModuleManager as shown in Fig. 3). This is primarily for convenience, but since it is in the PIMPL, they could be separated out later if need be.

With the exception of the “Loaded Module” component (which we discuss in more detail below). The remainder of the ModuleManagerPIMPL’s state was also present in top-level architecture diagram and has simply passed through. The design of the module manager component punts many of the module manager considerations to these components (see summary below), but provides user-facing APIs for doing them.

Module Pool

../../_images/module_pool.png

Fig. 5 Architecture of the “Loaded Module” component, i.e., a module pool.

The new component here is the “Loaded Module” component, which is an associative array of loaded modules used like a Module pool. Conceptually the main points of this pool are summarized in Fig. 5. Here our user has loaded one module under the module key "Module A" (we’ll discuss "Module B" below). Module keys are used to refer to the modules in the module pool. Each module choice has a unique key meant to facilitate referring back to a specific module.

When the user loads "Module A", "Module A" is inserted into the module pool as is. Fig. 5 depicts "Module A" as having four, members. The values of these four members, define the default state for "Module A". If a user does not want to use "Module A" in its default state, they can create a new configuration. This is what "Module B" represents in Fig. 5, i.e., "Module B" is a different configuration of "Module A" which differs in that the value of Member C is replaced with some new value Member E. Thus configurations are stored as differences. That is to say each configuration contains a link to the original module and a list of changes to apply. Note that by design, aside from the different value for the third member, to PluginPlay users and the call graph, "Module B" is indistinguishable from "Module A". This avoids placing any special emphasis on the default configuration.

Preserving the original state of the module is an important design point. First, it establishes a default configuration, from which we can define our differences. Second it facilitates recording provenance. When looking back at a previous session it can be important to know if the value used was the default value, or if the user set the value. Note that module defaults can change over time, which makes it harder to ascertain this information if it is not recorded. The third, and primary reason for preserving the original state is we necessarily treat each module as a black-box. Therefore we want to avoid copying the module on account of not knowing how expensive the copy actually will be.

Summary

The current design of the module manager really only directly addresses concern two (i.e., managing of Module pool) and punts the remaining concerns. More specifically:

  1. Dynamic module management

  • Operations go through ModuleManager class

  • Different configurations handled by module pool

  • Module pool is query-able and modifiable in real time

  • Call graph handles querying and modifying modules comprising program

  1. Managing of Module pool

    • Explicit component of the current design

  2. Modify call graph

    • User/call-graph interactions go through ModuleManager

    • Actual interactions fall to call graph component

  3. Run call graph

    • Running the call graph done through ModuleManager, but actual execution falls to call graph component

    • Efficiency tied to memoization and falls to the cache and call graph components

  4. Saving, loading, and restarting a previous session

    • User-facing API is exposed through ModuleManager

    • Falls to cache and call graph component (memoization)