refactor TaskScopes to use an aggregation tree (#5992)
### Description
Remove TaskScope and add a similar tree structure instead.
It aggregates Tasks in the task graph (parent-children) to aggregated
nodes which summarize important information from all contained tasks.
It's a tree-structure and any subgraph can be aggregated to a single
aggregated node to query these information in an efficient way.
We aggregate the following information from tasks:
* is there any unfinished task
* and an event when this reaches zero (so one can wait for it)
* collectibles emitted
* a list of dirty tasks
Receiving this information doesn't require to walk the tree since it's
eagerly aggregated when it changes on Tasks. Since it's a tree structure
updating such information on a Task has to walk the tree upwards which
is `O(R + log(R) * log(N))` where N is the number of Tasks in the graph
and R the number of roots of the graph.
It's also possible to query the aggregation from any Task to get
information about the roots. This has to walk the tree to every root
which should be `O(R + log(R) * log(N))`, but it's possible to shortcut
when any root already contains the information needed.
We use that to gather the following information about roots:
* is this root active
The tree is only connected from bottom to top. It's not possible to walk
the tree from top to bottom.
The tree is build from two parts, the top tree and the bottom tree.
A height 0 bottom tree will aggregate Tasks up to a configured
connectivity. The height 1 bottom tree will aggregate height 0 bottom
tree up to a configured connectivity. This continues recursively.
Since one doesn't know which height of bottom tree is needed to
aggregate a certain subgraph, a top tree is used. A depth 0 top tree
aggregates Tasks of infinite connectivity, by using a bottom tree of
height X and optionally a depth 1 top tree. This continues with depth 1
top tree using a height X + 1 bottom tree and a depth 2 top tree.
The connectivity and X are subject of fine tuning.
In general a Task can be in multiple bottom trees as inner node, but to
ensure tree reuse there is a limitation to that. Once a bottom tree
starts at a certain Task, it cannot be an inner node of other bottom
tree. So a Task is either non-root inner node in one or more bottom tree
or root node of exactly 1 bottom tree.
When a task is inner node in multiple bottom tree, the cost of the
children of the task will multiply with the number of bottom trees. This
can create a performance hit. To avoid that there is a threshold
(subject of fine tuning) which converts the Task into a root of a new
bottom tree when the multiple reaches the threshold.
The same limitations apply on higher level bottom trees.
Closes WEB-1621