Problem statement
There are times when a graph resource’s node and arc types may need to be extended at run-time rather than compile time. The current design provides type-safety but also requires all the node and arc types to be provided in a std::tuple<>
at build time. There are two motivating capabilities that drive the need for run-time graph specification
- User-provided organization of data and
- Python-defined graph-based resources.
An example of the former is when a user wishes to provide an ontology (such as in the web ontology language format). An ontology is effectively a specification of node and arc types. A good example is the Uberon ontology for anatomy. It defines over 20,000 node types and many relationship types with predefined arcs between many of the nodes.
Complications
While run-time graphs are desirable feature, they introduce some complications to the design of graph resources.
Unlike compile-time, type-safe arcs and nodes, it is possible for instances of run-time nodes and arcs to change their types (and thus invalidate the graph). Keeping search/traversal indexes updated as instances of run-time nodes and arcs are modified is also a challenge.
Potential solutions
Force everything at compile time
One solution is to force everything to occur at compile time; provide a utility to read an ontology and create a C++ implementation. This might suffice for some users (when those providing an ontrology have the technical ability to build a redistributable plugin with their new ontology’s node-types). It does not address Python-based resources.
Special “programmable” arc and node classes
Resources might always accept these programmable types by default but allow the node and arc types themselves to decide whether a particular node- or arc-insertion is allowed.
class ExtensibleResource
: public smtk::resource::DerivedFrom<
ExtensibleResource, smtk::graph::Resource>
{
public:
using Traits = ExtensibleTraits;
};
struct ExtensibleTraits
{
using NodeTypes = std::tuple<ProgrammableNode, …>;
using ArcTypes = std::tuple<ProgrammableArc, …>;
};
class ProgrammableNode : public smtk::graph::Component
{
void typeName() override { return m_nodeType; }
};
The ProgrammableArc
type would probably be an implicit arc; the current graph-resource design does not provide storage of data (such as arc “type”) on individual explicit arcs. If we use an explicit arc, then the resource API would need to be extended to deal with arc “type” separate from the existing endpoint interface API.
Resource maintains a “runtime whitelist”
In this case, the resource would keep a list of node and arc type-names that are allowed. The arcs would have to also provide a way to check that the from- and to- nodes are acceptable before insertion.
namespace smtk::graph {
class Resource
{
public:
void addRuntimeNodeType(
smtk::string::Token nodeTypeName,
std::function<std::shared_ptr<Component>()> nodeCtor
);
void addRuntimeArcType(
smtk::string::Token arcTypeName,
GenericEndpointInterfaceConstructor ctor);
// … and maybe:
void removeRuntimeNodeType(smtk::string::Token nodeTypeName);
void removeRuntimeArcType(smtk::string::Token arcTypeName);
/// Non-templated API for adding nodes
Component* createNodeOfType(smtk::string::Token nodeType);
};
// The base graph::Component class would need to be
// extended to include methods to access arcs at run time:
class Component
{
public:
GenericEndpointInterface incomingArcs(smtk::string::Token arcType);
ConstGenericEndpointInterface incomingArcs(smtk::string::Token arcType) const;
GenericEndpointInterface outgoingArcs(smtk::string::Token arcType);
ConstGenericEndpointInterface outgoingArcs(smtk::string::Token arcType) const;
};
} // namespace smtk::graph
Queries used to validate node and arc insertions
We could use the query mechanism that the base resource class exposes to delegate
- decisions about whether instances of a given node or arc type are allowed into a graph; and
- insertion, removal, and traversal/filtering of run-time nodes and arcs.
This solution would mean that the templated C++ API would be unable to perform these tasks for runtime nodes and arcs.