|
YOrch 1.0.0
|
YOrch uses yorch::task_tree to build a task tree. A task tree starts from one root node and then appends child nodes level by level.
The most common style is to pass a callable directly into the tree builder and describe its argument sources in the following (...):
There are two sets of parentheses:
.root(callable) / .node<Level>(callable) selects the callable placed on the tree.(...) describes where that callable gets its arguments.If the callable has no arguments, the second set of parentheses is still required and should be written as ().
root(...) registers the root task. Its level is always 0.
node<Level>(...) registers a normal node. Level is the node depth inside the tree:
node<1>(...) is a child of the root node.node<2>(...) is a child of the nearest available first-level node.You cannot skip levels while building a tree. For example, when the tree only has the root node, you cannot append node<2>(...) directly; a node<1>(...) must exist first.
An empty task_tree can also register the root with node<0>(...), but root(...) is usually preferred because it is clearer.
Each node can register a task in two ways.
This is the recommended shorthand. The tree builder internally calls the corresponding task registration API, so the syntax is shorter and works well in documentation and business code.
Use root(...) and node<Level>(...) for regular tasks:
Use root_into(...) and node_into<Level>(...) for direct-output tasks:
Use node_forward_prev<Level>(...) for forward-prev tasks:
A forward-prev task depends on the direct parent's output, so in an executable tree it is normally used as a non-root node.
You can also first create a task with the task registration API, bind its arguments, and then place that task into the tree.
This style is useful when task definitions and tree structure are maintained separately, or when a task must first be composed through adapters, factory functions, or local variables.
Direct-output tasks can be bound first as well:
The task already contains its argument descriptions, so .root(task) / .node<Level>(task) do not take an extra argument list.
Member function tasks can use the same pattern. You may first create a task object with task_member(...), task_into_member(...), or task_forward_prev_member(...), then place it into the tree with the normal .root(task), .root_into(task), .node<Level>(task), or .node_into<Level>(task) APIs.
A member function cannot be passed directly as a regular callable to root(...) or node<Level>(...), because it also needs a receiver. The tree builder provides separate shorthand APIs for member functions.
Use root_member(...) and node_member<Level>(...) for regular member functions:
Use root_into_member(...) and node_into_member<Level>(...) for direct-output member functions:
Use node_forward_prev_member<Level>(...) for forward-prev member functions:
The receiver is also an argument source description. It can come from value(...), from_ctx<T>(), borrow_prev<T>(), borrow_prev_mut<T>(), copy_prev<T>(), or consume_prev<T>().
When one parent node has multiple direct children and those children access the parent output, the fanout semantics must be considered.
The default policy is yorch::fanout_auto_policy. Usually it does not need to be written explicitly:
Its meaning is:
borrow_prev<T>(), borrow_prev_mut<T>(), copy_prev<T>(), and consume_prev<T>().borrow_prev<T>() and copy_prev<T>().borrow_prev_mut<T>() or consume_prev<T>(). If one child should consume the parent output while other children use copies, select fanout_consume_with_copies_policy explicitly.Use yorch::fanout_shared_readonly_policy when you want to explicitly state that the parent output is shared read-only by multiple children:
Use yorch::fanout_consume_with_copies_policy when one child may consume the parent output while other children use copied values:
When registering a callable directly, adapters(...) can also be used on the tree builder. This is equivalent in spirit to first binding with yorch::task(..., yorch::adapters(...)) and then registering the bound task, but the syntax is more centralized.
adapters(...) is placed in the first set of tree-builder parentheses, the .root(...) / .node<Level>(...) layer. The following (...) still contains only argument source specs.
For regular callables:
If the same node also specifies a fanout policy, the order is:
Member functions have one extra receiver argument, so adapters(...) is placed after the receiver:
If a member function also specifies a fanout policy, the order is:
root_into(...), node_into<Level>(...), root_into_member(...), and node_into_member<Level>(...) follow the same parameter placement rules.
adapters(...) receives concrete adapter descriptions. A policy needed by an adapter is passed to the adapter factory function, not as an independent tree-builder argument.
For example, the catch adapter has two forms:
yorch::adapt_catch_as_failure() uses the default catch policy.yorch::adapt_catch_as_failure(policy) uses a custom catch policy.The custom catch policy receives std::exception_ptr, must be callable with noexcept, and must return a value convertible to the task return type. For direct-output tasks, it returns yorch::step_result.
The retry adapter policy is also passed to the adapter factory:
When both fanout policy and adapter are present, the fanout policy is a parameter of the tree node, while policies such as retry_fixed_policy are parameters of the adapter itself:
Multiple adapters can be placed inside the same adapters(...). They are applied in written order: earlier adapters are closer to the original task, and later adapters wrap around the outside.
If a task object is already bound and then placed into the tree, adapters should be provided to yorch::task(...), yorch::task_into(...), or the corresponding member-function task registration API. .root(task) / .node<Level>(task) do not accept an additional adapters(...) argument.
A task tree is only a static structure. To execute it, compile it into a plan and then run the plan:
If a task uses from_ctx<T>(), pass the context to run_plan:
.root(f)(...) and .node<1>(f)(...)..root(task) and .node<Level>(task).root_into(...) / node_into<Level>(...) for direct-output tasks.node_forward_prev<Level>(...) for forward-prev tasks.root_member(...), node_member<Level>(...), and their into / forward_prev variants for member functions.