App components
As Wire-Cell Toolkit is a toolkit, it does not force a top-down
structure starting from a main()
function. But, it does provide a
layered top-down structure that applications may use after taking
their own choice of entry point. This post talks about a layer of
this structure called a WCT "app".
The top-down structure that WCT provides is layered roughly like this:
- environment / user code
- the
WireCell::Main
class - one or more
WireCell::IApplication
implementations - app-dependent components, for example,
Pgrapher
andTbbFlow
"app" objects use operate on data flow graph "node" components
App object life cycle via Main
When a Main
object is used to organize WCT components its "app"
objects have well defined life cycle.
- If the "app" implements the
IConfigurable
interface itsconfigure()
method will be called soon after any user configuration is parsed. After all configurable components are called, execution is returned to the user code. - The user code may then call
Main
(which is a callable object). It is typically called once per overall process execution but the user code may elect to do differently. When called, theMain
object will callexecute()
on every component which implementsIApplication
and which has been given toMain
as an "app" object". The order of calling is that of the order of the "apps" configuration attribute. - If an app object (or any component) implements
ITerminal
then itsfinalize()
method is called. The order of calling is the same order as the configuration sequence.
Command line and "app" objects
When using the wire-cell
command line you may specify "app" objects
with:
$ wire-cell -a app1 -a app2 [...] -c config.jsonnet
The names used must be associated with some component in the configuration.
Config file and "app" objects
Typically we "bake" in which applications to use by listing them in
the configuration using the special component type "wire-cell". This
only works for the wire-cell
CLI. Other environments such as for
interfacing with art provide similar special configuration means.
Here's the tail end of a configuration file that shows the Pgrapher
app being configured and told to the wire-cell
CLI:
local app = {
type: "Pgrapher",
data: {
edges: g.edges(graph),
},
};
local cmdline = {
type: "wire-cell",
data: {
plugins: ["WireCellGen", "WireCellPgraph", "WireCellSio", "WireCellSigProc"],
apps: ["Pgrapher"]
}
};
// Finally, the configuration sequence which is emitted.
[cmdline] + g.uses(graph) + [app]
Ordering of "app" calls
Typically, only one "app" object runs in a WCT job. In the rare case that multiple "app" objects run, there may be some care about the ordering. The order is as:
-
IConfigurable
are constructed in their order of the configuration sequence -
IApplication
are constructed in order of the "apps" list and if they are not alsoIConfigurable
. -
IConfigurable
haveconfigure()
called in their order of the configuration sequence - User code calls
Main
typically once which callsIApplication::execute()
on each "app" object in order of the "apps" list. - User code destroys
Main
which callsITerminal::finalize()
in the configuration sequence order.q
Global initialization and finalization
An "app" object may be used to enact a global initialization and finalization, eg to manage some global resource or code. Currently WCT can not guarantee truly first/last semantics. But, one can get close by adding an "app" to an existing job early in the "apps" list and late in the configuration sequence. Eg:
local graph = ...;
local global = { name: "Global", ... };
local cmdline = {
type: "wire-cell",
data: {
apps: ["Global", "Pgrapher"]
...
}
};
[cmdline] + g.uses(graph) + [app, global]
The "Global" app object may do relatively early operations in its
constructor. As long as no other components try to access the
"global" resource in their own constructors then "Global" will have
time to do what it needs. Since "Global" app is first in the "apps"
sequence it may "execute" just prior to the primary "app" object
(here, "Pgrapher"). Then it is placed last in the configuration
sequence so that it may get the final finalize()
call.