#include "babylon/anyflow/graph.h"
using babylon::anyflow::Graph;
using babylon::anyflow::GraphData;
using babylon::anyflow::Closure;
// The Graph instance is obtained via GraphBuilder::build and must be used exclusively.
// Generally, concurrency needs to be supported through thread_local or pooling techniques.
std::unique_ptr<Graph> graph = builder.build();
// Find the data named 'name'. If it doesn't exist, return nullptr.
// GraphData can be further used for retrieving or assigning values.
// The data set is collected from all
// named_depend(...).to("name")
// named_emit(...).to("name")
// for the specified 'name'.
GraphData* data = graph->find_data("name");
... // Initial value assignment
// Run the Graph instance to solve for the target data.
// The typical usage is to assign initial values to several nodes first, and then run to evaluate other terminal nodes.
// The execution is an asynchronous process, and progress can be managed via the returned closure.
Closure closure = graph->run(data);
... // Wait for completion and retrieve the result
// Reset the graph's execution state for reuse of the same Graph instance.
// Before resetting, ensure that the previous execution is completely finished, i.e., wait for the previous closure to fully finish (either destruct or closure.wait).
graph->reset();
#include "babylon/anyflow/data.h"
using babylon::anyflow::GraphData;
using babylon::anyflow::Committer;
using babylon::Any;
// GraphData is obtained via Graph::find_data.
GraphData* data = graph->find_data("name");
// Get a read-only value of type T from data. If data is unassigned or the type does not match, return nullptr.
// T can be ::babylon::Any, in which case the underlying value container will be returned, useful for advanced scenarios (e.g., operating on objects that cannot be default-constructed).
const T* value = data->value<T>();
const Any* value = data->value<Any>();
// Get a committer for type T from data. If data has already been assigned, an invalid committer will be returned.
// T can be ::babylon::Any, in which case the returned committer can directly operate on the underlying value container, useful for advanced scenarios (e.g., operating on objects that cannot be default-constructed).
Committer<T> committer = data->emit<T>();
Committer<Any> committer = data->emit<Any>();
if (committer) {
// Valid committer
} else {
// Invalid committer, possibly due to previous emission
}
// Convert and return the data as an object of type T. T supports basic types, equivalent to the underlying Any's as method.
// Automatic conversion is supported for intxx_t/uintxx_t/bool and other primitive types.
T value = data->as<T>();
// [Advanced] Pre-set the reference of some_exist_instance into data before the graph runs.
// When data->emit<T>() is called later, some_exist_instance will be used as the underlying instance.
// This is mainly used to optimize data transmission to external systems (e.g., communication framework layers) by allowing the graph to directly operate on external instances, avoiding unnecessary copies.
T some_exist_instance;
data->preset(some_exist_instance);
Committer<T> committer = data->emit<T>();
// committer.get() == &some_exist_instance
// [Advanced] Obtain the type declared for data.
// The declaration is completed through the GraphProcessor associated with data, using the ANYFLOW_INTERFACE macro.
const babylon::Id& type_id = data->declared_type();
#include "babylon/anyflow/data.h"
using babylon::anyflow::Committer;
// The committer is obtained from the GraphData::emit template function.
Committer<T> committer = data->emit<T>();
// Check if the committer is valid.
bool valid = committer.valid();
bool valid = (bool)committer;
// Use the committer as a pointer-like object to operate on the data to be emitted.
T* value = committer->get(); // Get raw pointer
committer->some_func(); // Directly call a function
*committer = value; // Assign a value, calling the assignment function
committer.ref(value); // Reference the value, where the lifecycle of the referenced value needs to be managed externally
committer.cref(value); // Reference an immutable value, where the lifecycle of the referenced value needs to be managed externally
// Logically clear the object to be emitted, as if emitting a nullptr.
committer.clear();
// Cancel data emission. When destructed, the object will not be emitted, as if data->emit<T> was never called.
committer.cancel();
// Confirm the data emission. After this, the object to be emitted can no longer be operated on.
committer.release();
~committer; // Destructor
#include <anyflow/closure.h>
using anyflow::Closure;
// Closure is obtained via Graph::run and is used to track the execution state.
Closure closure = graph->run(data);
// Check whether the execution is complete, i.e., the target data has been solved, or the execution has failed.
// 'Note' that even if the data is 'finished', there may still be residual nodes running.
bool finished = closure.finished();
// Get the return code. 0 indicates successful execution, and non-zero indicates failure.
// This only makes sense if finished is true.
int error_code = closure.error_code();
// Block and wait until finished, and retrieve the return code.
int error_code = closure.get();
// Wait until all the nodes triggered by the execution have completed. The wait will be automatically called upon destruction.
// Only after closure.wait() returns, the execution of the graph is fully finished, allowing for its destruction or reset for reuse.
closure.wait();
~closure; // Destructor automatically calls wait
// Asynchronously wait for the execution result. When finished, the callback will be called.
// Once on_finish is registered, the closure object becomes invalid and will not wait during destruction.
closure.on_finish([graph = std::move(graph)] (Closure&& finished_closure) {
// The passed finished_closure is equivalent to the closure that invoked on_finish.
// It can be reused to track execution.
// For instance, typically the graph can only be destroyed after all triggered nodes are finished running, so in the example, wait is called before destroying the captured graph.
finished_closure.wait();
graph.reset(nullptr);
// Note: In practice, this is unnecessary. After the callback finishes, the framework ensures that finished_closure is destructed before the lambda.
// Therefore, the order is correct as the graph is captured within the lambda.
});