Projects and Task Configuration
This page is to discuss some upcoming design decisions we have to
make about tasks, projects, their user interfaces, and workflows in general.
There are several issues that constrain how development proceeds.
Task and Project Overview
Before we describe issues and propose solutions, let’s settle on some terminology
- A project is a resource that collects other resources and assigns them roles.
- A project’s resources are indexed by their roles.
- A project owns a directory on the filesystem but is encoded as an index file inside that directory. Resources which belong to the project are, by default, kept in the project’s directory.
- A task is some work that must be performed by a user, but whose completability can be measured programmatically.
- Only one task can be active at a time. Changes to which task is active can be observed by UI elements.
- Each task can be in one of several states:
State Description Irrelevant The user’s work in prior tasks mean this task needs no user input. Unavailable The task’s dependencies are unmet. Incomplete The task is available but its objective is not accomplished. Completable The task is available and accomplished but has not been marked complete. Completed The task has been marked completed by the user. - Task state transitions may be observed.
- A task may have a dependency on another task. If task B depends on task A, then B will be Unavailable until A is marked Completed.
- Tasks may store configuration information in JSON, including whether a user has marked them complete or not.
- When a task changes state, any number of task adaptors observing it may run. These adaptors modify the configuration of other, related tasks.
- Thus, there are two types of barriers that prevent tasks from reaching completion:
- Administrative barriers, in the form of a designer-mandated dependency on the completion of some prior task.
- Technical barriers, in the form of incomplete configuration because a prior or co-occurring task’s adaptor has not run yet.
- A task may have any number of style identifiers, which can be used to configure user interface elements when it becomes active. For example, an application plugin may provide a behavior which switches what SMTK components are visible whenever the active task changes. Rather than assume the C++ type of the task determines which components should be visible, the behavior asks the task for its style identifiers and searches its configuration to decide which components to hide/show.
Example configuration
To make tasks more clear, let’s consider a pretend example workflow with 3 tasks:
- Gather geometric model resources for a combined structural and electromagnetic (EM) simulation.
- Assign boundary conditions to model components.
- Assign materials to model components.
The high-level task configuration looks like this:
{
/* Task configuration: dependencies, style identifiers, & state. */
"tasks": [ … ],
/* Task-adaptor configuration: tasks to adapt and how to do so. */
"adaptors": [ … ],
/* Map from style identifiers to user interface configuration. */
"styles": { … },
/* Dictionary of smtk::view::Configuration objects.
* These currently reside in attribute resources, but placing
* them here is an alternative.
*/
"views": { … }
}
The tasks
section might look like this after a user has completed the first task:
[ { "id": 1, "type": "smtk::task::GatherResources",
"title": "Load geometry and spec", "state": "completed",
"resources-by-role": [
{ "role": "structural simulation",
"type": "smtk::markup::Resource",
"resources": [ "deadbeef-…" ]},
{ "role": "cavity geometry", "max": 2,
"type": "smtk::model::Resource",
"resources": [ "feedface-…" ]},
{ "role": "solver config",
"type": "smtk::attribute::Resource",
"resources": [ "b44dc0de-…" ]}
]},
{ "id": 2, "type": "smtk::task::FillOutAttributes",
"title": "Mark up materials", "state": "incomplete",
"dependencies": [ 1 ],
"style": ["material-editing", "show-volumes"],
"components-by-role": [
{ "role": "material",
"attributes": [
{ "resource-role": "simulation attribute",
"resource-type": "smtk::attribute::Resource",
"component": "*[type=Material]",
"matching-components": [ "deadc0de-…" ]
}]
}
]},
{ "id": 3, "type": "smtk::task::FillOutAttributes",
"title": "Mark up BCs", "state": "incomplete",
"dependencies": [ 1 ],
"style": ["bc-editing", "show-surfaces"],
"components-by-role": [
{ "role": "boundary condition",
"attributes": [
{ "resource-role": "simulation attribute",
"resource-type": "smtk::attribute::Resource",
"component": "*[type=BoundaryCondition]",
"matching-components": [ "badc0de5-…" ]
}]
}
]} ]
Note that because the first task has been completed, task adaptors for task 1 – which update the configuration of tasks 2 & 3 – have run; these adaptors might have set up resource UUIDs (based on user-assigned resource roles) on tasks 2 & 3.
Also note that tasks 2 & 3 have style identifiers listed. Let’s consider what the style
section of the workflow configuration might look like based on a couple of the styles mentioned above:
{
/* User interface configurations when assigning materials: */
"material-editing": {
/* How to configure the attribute editor */
"attribute-editor": {
/* Name a configuration in the 'views' section below */
"view-configuration": "MaterialView"
},
/* We don't have a context menu behavior yet,
but you can imagine wanting to add material
assignment options to it. */
"context-menu": { /* … */ } },
/* User interface configs for viewing volume meshes. */
"show-volumes": {
/* We don't have a 3-D view behavior yet,
but you can imagine wanting to specify
the color-by mode and component visibilities. */
"representation": {
"color-by": { /* … */ },
"visiblity": {
"on": { [ "smtk::model::resource", "volume" ] }
"off": { [ "smtk::model::resource", "face" ] }
} }
},
/* User interface configurations when assigning
boundary conditions: */
"bc-editing": {
/* How to configure the attribute editor */
"attribute-editor": {
"view-configuration": {
/* You could also in-line view configuration here.
* instead of refering to an entry in the next section.*/
},
/* The color-by mode and component visibilities
* are different for boundary conditions. */
"representation": {
"color-by": { /* … */ },
"visiblity": {
"on": { [ "smtk::model::resource", "volume" ] }
"off": { [ "smtk::model::resource", "face" ] }
} }
/* How to configure the resource browser */
"resource-panel": {
"phrase-model" : {
"type": "smtk::view::ComponentPhraseModel",
"subphrase-generator": "smtk::view::SubphraseGenerator",
"badges": [ /* … */ ]
} }
} }
}
Note that in the example above, the 3-D representation
configuration is under show-volumes
. Because task 2 has two style identifiers listed (material-editing
and show-volumes
), both will be applied when task 2 becomes active. Being able to assign multiple style identifiers to a single task makes it easy to re-use UI element configurations in several different tasks. For example, we might have another task for mesh inspection where we would also want to show volume components and hide face components. That task might be marked with show-volumes
but not material-editing
.
Issues
Now let’s examine the issues our design has uncovered.
Preserving state vs preserving provenance
Because tasks serialize themselves and their configuration to JSON, it is easy to preserve state. For example, task 2’s state above had specific resources called out by UUID once task 1’s adaptor ran. Portions of this state may change over time as users make decisions. We want to serialize/deserialize the current values in order to get users back to their save point. However, from a provenance viewpoint, it is important to save the time-history of the state in order to be reproducible.
- Should we allow both? If so, how?
- If we save both, how can we record and display history without it cluttering the current set of tasks a user is focused on?
Designer-specified versus user-specified workflows
In a related fashion, some applications or users want the ability to edit the set of tasks in a workflow to allow iterative exploration/design while other applications or users need to keep the set of tasks fixed to ensure infrequent or novice users are guided through with minimal flexibility (to avoid complexity and clutter).
- What is an appropriate interface for accepting task edits by advanced users? How can we know what portions of a task configuration should be copied or not when a user wants to redo a task?
- How do we make it clear what types of data are passed between tasks so users can connect and configure them properly?
- Also, how would migration to new versions of a project schema work?
Style and preferences
Style identifiers provide a flexible way to tailor user interface elements to each task. However, sometimes users have preferences that may conflict with the styles specified by the workflow designer.
- How can we resolve these conflicts?
Storing configuration
We have discussed storing the JSON task configuration in the example above inside SMTK projects.
- Is that the best location for it? It presumes that in order to use workflows you must use projects.
- Similarly, should we store “current state” with the project but store the “template” for tasks as provided by the workflow designer somewhere else? Storing it somewhere else means common configuration is not part of every user document and might simplify upgrading documents as the workflow changes. However, it complicates things by forcing modelbuilder to look in multiple places each time a project is opened.