Strongly-typed, dependency based application framework for code/data separation with dependency injection and data passing.
DetectorGraph is a framework for writing programs in a formal graph topology. This can be used to write applications with multiple interdependent algorithms, applications' data models, general business logic or all of that combined. The framework uses a formal distinction between data (Topics) and transformations/logic (Detectors). It natively provides dependency injection, strict type-safety and provides loose coupling between Detectors by formalizing the touch points as Topics. It forces an intuitive (albeit unusual) programming paradigm that results in highly readable, maintainable & testable code.
This is not an officially supported Google product.
Note that the cross-reference links in this page are only rendered in the Doxygen version of the documentation (see Building). You can also navigate the web version of the documentation hosted at https://google.github.io/detectorgraph/.
Applications are written as a combination of Detectors and Topics.
Topics carry a particular signal/data-type:
struct FooSensorData : public DetectorGraph::TopicState
{
int x;
}
Detectors encode a logical unit that describes the transformation from any number of Topics to any number of other Topics:
class BarThresholdDetector : public DetectorGraph::Detector,
public DetectorGraph::SubscriberInterface<FooSensorData>,
public DetectorGraph::Publisher<BazThresholdCrossing>
{
BarThresholdDetector(DetectorGraph::Graph* graph) : DetectorGraph::Detector(graph)
{
Subscribe<FooSensorData>(this);
SetupPublishing<BazThresholdCrossing>(this);
}
virtual void Evaluate(const FooSensorData& data)
{
if (data.x > 100)
{
Publish(BazThresholdCrossing(data.x));
}
}
}
DetectorGraphs are created by adding any number of Detectors to a Graph. All necessary Topics are created on demand and supplied via Dependency Injection.
DetectorGraph::Graph graph;
BarThresholdDetector detector(&graph);
Detectors and Topics are kept sorted in topological order.
Graph Evaluations start after data is posted to a Topic. This causes all Detector's [Evaluate()
](@ref DetectorGraph::SubscriberInterface::Evaluate) methods for that Topic to be called with the new piece of data which in turn may result in new data being posted to subsequent Topics. That may then trigger the Evaluate()
of other Detectors. This process continues following the topological order until the end of the Graph is reached or until no more Topics with subscribers have new data.
Graphs can be visualized with Graphviz
Below are a number of examples showcasing different aspects & usage patterns of the framework (these are in the ./examples/
folder).
Basic
Feedback Loops
Timeouts & Timers
Dealing with Large TopicStates
Initial State & State Persistence
The library has no dependencies beyond C++ & STL (min C++0x) and was designed to be fully portable to any environment supporting those dependencies.
The library uses abstractions for basic things like asserts & logging to allow for project & platform specific customization.
The library is shipped with a basic set of implementations for those basic functions in ./platform_standalone
.
To enable time-aware functionality (e.g. [PublishOnTimeout
](@ref DetectorGraph::TimeoutPublisher), [GetTime
](@ref DetectorGraph::TimeoutPublisherService::GetTime), [SetupPeriodicPublishing
](@ref DetectorGraph::Detector::SetupPeriodicPublishing)) you must provide a concrete implementation of [TimeoutPublisherService
](@ref DetectorGraph::TimeoutPublisherService) and pass that to your Detectors upon construction.
There are multiple ways of integrating [Graph
](@ref DetectorGraph::Graph) into your application. A good place to start is sub-classing [ProcessorContainer
](@ref DetectorGraph::ProcessorContainer), adding:
- Your Detectors
- Plumbing that connects your data sources (e.g. drivers, buttons, network) to calls to [ProcessData()
](@ref DetectorGraph::ProcessorContainer::ProcessData) for specific input Topics
- An implementation of [ProcessOutput()
](@ref DetectorGraph::ProcessorContainer::ProcessOutput) that links specific output Topics to your application's outputs (e.g. LEDs, actuators, network)
A more powerful & flexible option is to implement your own container to hold the [Graph
](@ref DetectorGraph::Graph) and [Detector
](@ref DetectorGraph::Detector) objects and orchestrate calls to [PushData<T>()
](@ref DetectorGraph::Graph::PushData), [EvaluateGraph()
](@ref DetectorGraph::Graph::EvaluateGraph) and either inspect particular Topics (retrievable via [ResolveTopic<T>()
](@ref DetectorGraph::Graph::ResolveTopic)) or iterate through all modified Topics using [GetOutputList()
](@ref DetectorGraph::Graph::GetOutputList).
The library is shipped with a bare-bones makefile that can be used to build & run all of the examples, unit tests, documentation and coverage report.
Dependencies:
Building:
# Hello World
~/detectorgraph$ make examples/helloworld
# Build/Run unit tests
~/detectorgraph$ make unit-test/test_lite
~/detectorgraph$ make unit-test/test_full
# Or simply:
~/detectorgraph$ make unit-test/test_all
# Build Docs. Results in /docs
~/detectorgraph$ make docs
# Build/Run Coverage test. Results in /coverage
~/detectorgraph$ make unit-test/test_coverage
The library is mostly header-only (only 3 core compilation units) and has a trivial compilation process. So instead of providing a binary or a complete build system we recommend that users use their build system of choice to build the library in whatever way fits their needs better. For examples on how to do that, please check [makefile
](@ref makefile).
DetectorGraph shares a lot of its core concepts with other frameworks based in computation graphs (e.g. ROS, TensorFlow etc) but has also many differences. Some of its most unique features are:
For an article containing a set of guidelines & rules of thumb that we accumulated after 3+ years of using DetectorGraph visit [Style Tips - Patterns, Anti-Patterns & Suggestions](@ref ssg-style_suggestions). These are aimed at keeping software design constrained in a way that best takes advantage of the DetectorGraph framework, its expressibility and modeling power.
The DetectorGraph library had a little naming problem growing up. From birth it came to replace nlDetectorGraph and so it pretended to be called that way. As an adolescent it decided it wanted to be called MarkII.. but no one cared - for years now the world continues to call it simply DetectorGraph. Now as it approaches adulthood it has finally accepted its popular name: DetectorGraph.
For in depth documentation of the library, [start here](@ref core_introduction).