This helper was drafted for the Job / JobPlanning and Scheduler interface in 2013, but seemingly not yet put into action. While in the original use case, we have a genuine measuerment for the tree depth (given by the depth of the processing stack), in other use cases we want to use to offset embedded within the indicator itself for keeping track of the depth. Thus I add a second mark operation, which usess the current offset to set a new reference level. This has the consequence that the offset has now to reflect the new reference point immediately
421 lines
13 KiB
C++
421 lines
13 KiB
C++
/*
|
|
HierarchyOrientationIndicator(Test) - mechanism for reproducing a tree (of jobs)
|
|
|
|
Copyright (C) Lumiera.org
|
|
2013, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
* *****************************************************/
|
|
|
|
|
|
#include "lib/test/run.hpp"
|
|
|
|
#include "lib/hierarchy-orientation-indicator.hpp"
|
|
#include "lib/iter-adapter-stl.hpp"
|
|
#include "lib/iter-explorer.hpp"
|
|
#include "lib/itertools.hpp"
|
|
#include "lib/util.hpp"
|
|
|
|
#include <boost/operators.hpp>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdlib>
|
|
|
|
|
|
namespace lib {
|
|
namespace test {
|
|
|
|
namespace { // test fixture: a random Tree to navigate...
|
|
|
|
using std::rand;
|
|
using std::function;
|
|
using lib::transformIterator;
|
|
using lib::iter_stl::eachAddress;
|
|
using util::contains;
|
|
using util::max;
|
|
|
|
/* -- size of the test tree ---- */
|
|
const uint MAX_CHILDREN_CNT(5); // children per Node (5 means 0 to 4 children)
|
|
const double CHILD_PROBABILITY(0.45); // probability for a Node to have any children
|
|
const uint TEST_SEQUENCE_LENGTH(50); // test uses a sequence of Node trees
|
|
// 5 - 45% - 50 produce roughly 1000 Nodes and tree depths of about 12
|
|
uint nextChildID(1);
|
|
|
|
|
|
/**
|
|
* pick a random child count below #MAX_CHILDREN_CNT
|
|
* with a probability to get any count above zero
|
|
* as defined by CHILD_PROBABILITY
|
|
*/
|
|
inline uint
|
|
pick_random_count()
|
|
{
|
|
uint bottom((1.0/CHILD_PROBABILITY - 1) * MAX_CHILDREN_CNT);
|
|
uint limit = bottom + MAX_CHILDREN_CNT;
|
|
ASSERT (0 < bottom);
|
|
ASSERT (bottom < limit);
|
|
|
|
int cnt = (rand() % limit) - bottom;
|
|
return max(0, cnt);
|
|
}
|
|
|
|
/** (sub)tree of test data */
|
|
struct Node
|
|
: boost::equality_comparable<Node>
|
|
{
|
|
typedef std::vector<Node> Children;
|
|
|
|
int id_;
|
|
Children children_;
|
|
|
|
|
|
Node(int i) ///< build node explicitly without children
|
|
: id_(i)
|
|
{ }
|
|
|
|
|
|
Node() ///< build a random test subtree
|
|
: id_(nextChildID++)
|
|
{
|
|
uint c = pick_random_count();
|
|
for (uint j=0; j<c; ++j) // populate with c random children
|
|
children_.push_back(Node());
|
|
}
|
|
|
|
Node const&
|
|
child (uint i) const
|
|
{
|
|
REQUIRE (i < children_.size());
|
|
return children_[i];
|
|
}
|
|
|
|
bool
|
|
hasChild (Node const& o)
|
|
{
|
|
return util::contains (children_, o);
|
|
}
|
|
|
|
Node&
|
|
makeChild (int childID)
|
|
{
|
|
children_.push_back (Node(childID));
|
|
return children_.back();
|
|
}
|
|
};
|
|
|
|
|
|
inline bool
|
|
have_equivalent_children (Node const& l, Node const& r)
|
|
{
|
|
if (l.children_.size() != r.children_.size()) return false;
|
|
for (uint i=0; i<l.children_.size(); ++i)
|
|
if (l.child(i) != r.child(i)) return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
operator== (Node const& l, Node const& r)
|
|
{
|
|
return l.id_ == r.id_
|
|
&& have_equivalent_children(l,r);
|
|
}
|
|
|
|
|
|
typedef lib::IterQueue<Node*> NodeSeq;
|
|
|
|
|
|
/**
|
|
* Function to generate a depth-first tree visitation
|
|
*/
|
|
NodeSeq
|
|
exploreChildren (Node* node)
|
|
{
|
|
NodeSeq children_to_visit;
|
|
build(children_to_visit).usingSequence (eachAddress (node->children_));
|
|
return children_to_visit;
|
|
}
|
|
|
|
|
|
|
|
struct VisitationData
|
|
{
|
|
int id;
|
|
int orientation;
|
|
|
|
VisitationData(int refID,
|
|
int direction =0)
|
|
: id(refID)
|
|
, orientation(direction)
|
|
{ }
|
|
};
|
|
|
|
/**
|
|
* This functor visits the nodes to produce the actual test data.
|
|
* The intention is to describe a visitation path through a tree structure
|
|
* by a sequence of "up", "down", and "level" orientations. The test we're
|
|
* preparing here will attempt to re-create a given tree based on these
|
|
* directional information. The actual visitation path is created by
|
|
* a depth-first exploration of the source tree.
|
|
*/
|
|
class NodeVisitor
|
|
{
|
|
typedef std::deque<Node*> NodePath;
|
|
typedef NodePath::reverse_iterator PathIter;
|
|
|
|
NodePath path_;
|
|
|
|
public:
|
|
// using default ctor and copy operations
|
|
|
|
static function<VisitationData(Node*)>
|
|
create () { return NodeVisitor(); }
|
|
|
|
|
|
VisitationData
|
|
operator() (Node* node)
|
|
{
|
|
int direction = establishRelation (node);
|
|
return VisitationData(node->id_, direction);
|
|
}
|
|
|
|
private:
|
|
/** Helper for this test only: find out about the hierarchical relation.
|
|
* In the real usage situation, the key point is that we \em record
|
|
* this relation on-the-fly, when visiting the tree, instead of
|
|
* determining it after the fact. */
|
|
int
|
|
establishRelation (Node* nextNode)
|
|
{
|
|
REQUIRE (nextNode);
|
|
uint level = path_.size();
|
|
uint refLevel = level;
|
|
for (PathIter p = path_.rbegin();
|
|
0 < level ; --level, ++p )
|
|
{
|
|
Node* parent = *p;
|
|
if (parent->hasChild (*nextNode))
|
|
{
|
|
// visitation continues with children below this level
|
|
path_.resize(level);
|
|
path_.push_back(nextNode);
|
|
return (level - refLevel) + 1;
|
|
}
|
|
}
|
|
ASSERT (0 == level);
|
|
|
|
// nextNode not found as child (i.e. fork) within current tree path
|
|
// --> start new tree path at root
|
|
path_.clear();
|
|
path_.push_back(nextNode);
|
|
return (0 - refLevel) + 1;
|
|
} // by convention, root is an implicitly pre-existing context at level 0
|
|
};
|
|
|
|
|
|
/**
|
|
* the core of this test: rebuilding a tree
|
|
* based on visitation data, including the \em orientation
|
|
* of the visitation path (up, down, siblings). After construction,
|
|
* the embedded #children_ will reflect the original sequence as
|
|
* described by the given treeTraversal.
|
|
* @remarks this is a blueprint for the scheduler interface,
|
|
* which accepts a sequence of jobs with dependencies.
|
|
*/
|
|
struct TreeRebuilder
|
|
: Node
|
|
{
|
|
template<class IT>
|
|
TreeRebuilder (IT treeTraversal)
|
|
: Node(0)
|
|
{
|
|
populate (transformIterator (treeTraversal,
|
|
NodeVisitor::create()));
|
|
}
|
|
|
|
private:
|
|
template<class IT>
|
|
void
|
|
populate (IT treeVisitation)
|
|
{
|
|
struct Builder
|
|
{
|
|
Builder (Node& startPoint)
|
|
: parent_(NULL)
|
|
, current_(&startPoint)
|
|
{ }
|
|
|
|
void
|
|
populateBy (IT& treeVisitation)
|
|
{
|
|
while (treeVisitation)
|
|
{
|
|
int direction = treeVisitation->orientation;
|
|
if (direction < 0)
|
|
{
|
|
treeVisitation->orientation += 1;
|
|
return;
|
|
}
|
|
else
|
|
if (direction > 0)
|
|
{
|
|
treeVisitation->orientation -= 1;
|
|
Node* refPoint = startChildTransaction();
|
|
populateBy (treeVisitation);
|
|
commitChildTransaction(refPoint);
|
|
}
|
|
else
|
|
{
|
|
addNode (treeVisitation->id);
|
|
++treeVisitation;
|
|
}}}
|
|
|
|
private:
|
|
Node* parent_;
|
|
Node* current_;
|
|
|
|
void
|
|
addNode (int id)
|
|
{
|
|
current_ = & parent_->makeChild(id);
|
|
}
|
|
|
|
Node*
|
|
startChildTransaction()
|
|
{
|
|
Node* oldRefPoint = parent_;
|
|
ASSERT (current_);
|
|
parent_ = current_; // set new ref point
|
|
return oldRefPoint;
|
|
}
|
|
|
|
void
|
|
commitChildTransaction(Node* refPoint)
|
|
{
|
|
parent_ = refPoint;
|
|
current_ = parent_;
|
|
}
|
|
};
|
|
|
|
|
|
Builder builder(*this); // pre-existing implicit root context
|
|
builder.populateBy (treeVisitation);
|
|
}
|
|
};
|
|
|
|
|
|
} //(End) test fixture
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************//**
|
|
* @test describing and rebuilding a tree structure
|
|
* while visiting the tree in depth first order.
|
|
* To keep track of the level changes during that navigation,
|
|
* we use an indicator to represent the relative level difference
|
|
* compared to the previously visited node in the tree.
|
|
*
|
|
* @see HierarchyOrientationIndicator
|
|
* @see DispatcherInterface_test
|
|
*/
|
|
class HierarchyOrientationIndicator_test : public Test
|
|
{
|
|
|
|
virtual void
|
|
run (Arg)
|
|
{
|
|
demonstrate_tree_rebuilding();
|
|
verify_OrientationIndicator();
|
|
}
|
|
|
|
/** @test demonstrate how a Node tree structure can be rebuilt
|
|
* just based on the visitation sequence of an original tree.
|
|
* This visitation captures the local data of the Node (here the ID)
|
|
* and the orientation of the visitation path (down, next sibling, up)
|
|
*
|
|
* This is a demonstration and blueprint for constructing the scheduler interface.
|
|
* The Scheduler accepts a series of new jobs, but jobs may depend on each other,
|
|
* and the jobs are created while exploring the dependencies in the render engine's
|
|
* node graph (low-level-model).
|
|
*/
|
|
void
|
|
demonstrate_tree_rebuilding ()
|
|
{
|
|
Node::Children testWood;
|
|
for (uint i=0; i < TEST_SEQUENCE_LENGTH; ++i)
|
|
testWood.push_back(Node());
|
|
|
|
TreeRebuilder reconstructed (depthFirst (eachAddress(testWood)) >>= exploreChildren);
|
|
|
|
CHECK (reconstructed.children_ == testWood);
|
|
}
|
|
|
|
|
|
void
|
|
verify_OrientationIndicator ()
|
|
{
|
|
OrientationIndicator orient;
|
|
|
|
CHECK (0 == orient);
|
|
++orient;
|
|
CHECK (+1 == orient);
|
|
----orient;
|
|
CHECK (-1 == orient);
|
|
orient.markRefLevel();
|
|
CHECK ( 0 == orient);
|
|
|
|
orient.markRefLevel (2);
|
|
CHECK (-3 == orient);
|
|
|
|
orient.markRefLevel (2);
|
|
CHECK (-3 == orient);
|
|
|
|
orient -= orient;
|
|
CHECK ( 0 == orient);
|
|
|
|
++orient;
|
|
CHECK (+1 == orient);
|
|
orient.markRefLevel();
|
|
|
|
orient.markRefLevel (4);
|
|
orient.markRefLevel (5);
|
|
CHECK (-2 == orient);
|
|
|
|
orient.markRefLevel (2);
|
|
CHECK (+1 == orient);
|
|
orient.resetToRef();
|
|
++orient;
|
|
orient.markRefLevel(); // now at level == 3
|
|
CHECK ( 0 == orient);
|
|
|
|
orient += 200;
|
|
orient -= 190;
|
|
CHECK (+7 == orient);
|
|
|
|
OrientationIndicator o2(orient);
|
|
o2.markRefLevel(12);
|
|
CHECK (-2 == o2);
|
|
CHECK (+7 == orient);
|
|
}
|
|
};
|
|
|
|
|
|
/** Register this test class... */
|
|
LAUNCHER(HierarchyOrientationIndicator_test, "unit common");
|
|
|
|
}} // namespace lib::test
|