|
YOrch 1.0.0
|
The executor runs a compiled plan. The usual flow is:
You can think of execution as four steps:
run_plan(...) and check the execution result.A task tree is more like a build-time structure. It records which tasks exist and how they are related by level. Before execution, call compile_plan(...):
The plan is the object read by the executor. It stores task nodes, parent-child relationships, argument sources, and slot information, and it also carries pre-execution constraint checks. For example, an empty task tree, invalid prev-access, or invalid forward-prev / fanout policy combinations are rejected before execution starts.
context stores shared data for one execution. A task can declare an argument from context with from_ctx<T>(); at execution time, pass the context to run_plan(...).
If multiple tasks need shared configuration, loggers, cache handles, or runtime state, it is recommended to give the context type a business name with using. That keeps construction, passing, and function signatures clearer:
context<Ts...> stores objects by type. Each type can appear only once in the same context, so task parameters can unambiguously find their source by type. The executor only borrows the context during the current run_plan(...) call; it does not create or extend the lifetime of the context.
If tasks do not need context, call:
run_plan(...) can use default settings or select optional strategies through template parameters. There are currently two main policy categories:
Policies are written as template parameters of run_plan(...) in this fixed order:
If execution also needs context, policies are still written as template parameters, and the context remains a normal function argument:
The default call is usually enough. It is equivalent to using slot_layout_one_to_one_policy and exec_serial_dfs_recursive_policy:
slot_layout_one_to_one_policy is the default slot layout policy. It prepares an independent physical slot for every task node that has an output payload.
This policy is the most direct. It is useful when first using the library, debugging execution flow, or wanting the relationship between slots and task nodes to be easy to understand. Since it is the default, it usually does not need to be written explicitly:
To write the default slot layout explicitly, place it in the first template parameter position of run_plan:
slot_layout_serial_dfs_compact_policy uses the current synchronous serial DFS execution order to reuse slot storage whose lifetimes do not overlap.
This policy fits deeper task trees, larger payloads, or cases that want to reduce temporary runtime storage. It does not change task execution order or business semantics; it only changes how the executor internally arranges slots.
Use it as the first template parameter of run_plan:
exec_serial_dfs_recursive_policy is the default execution policy. It enters child nodes recursively through the C++ call stack. Semantically, it is synchronous serial DFS: execute the root, enter children after the parent succeeds, complete a subtree, and then return to later siblings.
Since it is the default, it usually does not need to be written explicitly:
If you need to write the execution policy explicitly, it must be in the second template parameter position. Because the first position belongs to the slot layout policy, both policies must be written:
exec_serial_dfs_explicit_heap_stack_policy keeps the same synchronous serial DFS semantics, but it does not enter child nodes through the C++ call stack. It uses an explicit heap stack to store traversal state.
This policy is useful when a task tree may be very deep, or when you want to avoid deep recursive call stacks. It is still synchronous, serial, and DFS; it does not run tasks concurrently.
Write the slot layout policy in the first template parameter position and the execution policy in the second:
It can also be combined with compact slot layout:
To keep call sites shorter, use using aliases for common combinations:
These policies do not change the business semantics of the tasks. They only affect how the executor stores intermediate results, how it traverses the task structure, and whether it uses the recursive call stack or an explicit heap stack. They all currently belong to the synchronous serial DFS execution model for task trees.
The currently supported execution mode for task trees is synchronous serial DFS.
Synchronous means run_plan(...) completes on the current thread and returns a result. Serial means only one task runs at a time. DFS means the executor uses depth-first order: start from the root, enter the first child and its descendants after the parent succeeds, and then return to later siblings.
In this mode, the plan structure, node relationships, and most execution paths are already known at compile time. The executor tries to use that static information for optimization and expansion, pushing scheduling logic toward compile time and avoiding extra dynamic scheduling, runtime lookups, or type erasure just to express task orchestration.
For this structure:
The current execution order is:
If a task returns a failure state, the executor uses the result semantics to stop the current branch or terminate the whole plan. This keeps failure propagation and resource lifetimes clear, and matches the task-tree model: a subtree only has the precondition to run after its parent chain has succeeded.
More execution modes will be added later:
The goal is to reuse the same task registration and task structure while replacing only the execution-stage scheduling model.
run_plan(...) returns yorch::step_result. The common pattern is to check whether the whole plan completed successfully with ok():