Skip to content

Commit

Permalink
Refactor|libdeng2: PathTree nodes index their own children
Browse files Browse the repository at this point in the history
Branch nodes now have the ability to keep track on who their immediate
child nodes are. This is a crucial property of a tree data structure,
and required by Archive (and later, FS2).

Now when PathTree is asked to traverse the nodes matching a parent,
it can simply look at only the child nodes of the parent rather than
iterating through all the nodes of the tree.

Of course, the user is free to iterate the children manually using
PathTree::Node::children() (and maybe a PathTreeIterator).

This behavior can be disabled with the PathTree::NoLocalBranchIndex
flag, if the user of the tree needs no access to a specific parent's
children.
  • Loading branch information
skyjake committed Nov 26, 2012
1 parent 0c5310c commit 7a224e9
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 38 deletions.
32 changes: 26 additions & 6 deletions doomsday/libdeng2/include/de/data/pathtree.h
Expand Up @@ -66,12 +66,16 @@ class DENG2_PUBLIC PathTree
public:
class Node; // forward declaration

typedef QMultiHash<Path::hash_type, Node *> Nodes;
typedef QList<String> FoundPaths;

/**
* Flags that affect the properties of the tree.
*/
enum Flag
{
MultiLeaf = 0x1 ///< There can be more than one leaf with a given name.
MultiLeaf = 0x1, ///< There can be more than one leaf with a given name.
NoLocalBranchIndex = 0x2 ///< Branch nodes will not have an index of their immediate child nodes.
};
Q_DECLARE_FLAGS(Flags, Flag)

Expand Down Expand Up @@ -140,6 +144,9 @@ class DENG2_PUBLIC PathTree
*/
class DENG2_PUBLIC Node
{
public:
typedef PathTree::Nodes Children;

protected:
Node(NodeArgs const &args);

Expand All @@ -153,13 +160,22 @@ class DENG2_PUBLIC PathTree
/// this is the tree's special root node.
Node &parent() const;

/// Returns the children of a branch node. Note that leaf nodes
/// have no children -- calling this for leaf nodes is not allowed.
Nodes const &children() const;

/// Determines if the node is at the root level of the tree
/// (no other node is its parent).
bool isAtRootLevel() const;

/// @return @c true iff this node is a leaf.
bool isLeaf() const;

/// @return @c true iff this node is a branch.
inline bool isBranch() const {
return !isLeaf();
}

/// @return Type of this node.
inline NodeType type() const {
return isLeaf()? Leaf : Branch;
Expand Down Expand Up @@ -204,6 +220,8 @@ class DENG2_PUBLIC PathTree

protected:
SegmentId segmentId() const;
void addChild(Node &node);
void removeChild(Node &node);

private:
struct Instance;
Expand All @@ -214,9 +232,6 @@ class DENG2_PUBLIC PathTree
/// The requested entry could not be found in the hierarchy.
DENG2_ERROR(NotFoundError);

typedef QMultiHash<Path::hash_type, Node *> Nodes;
typedef QList<String> FoundPaths;

public:
explicit PathTree(Flags flags = 0);

Expand All @@ -225,10 +240,15 @@ class DENG2_PUBLIC PathTree
/// @return @c true iff there are no paths in the hierarchy. Same as @c size() == 0
bool empty() const;

/// @return Total number of unique paths in the hierarchy.
inline bool isEmpty() const { return empty(); }

/// Returns the flags that affect the properties of the tree.
Flags flags() const;

/// Total number of unique paths in the hierarchy.
int size() const;

/// @return Total number of unique paths in the hierarchy. Same as @ref size().
/// Total number of unique paths in the hierarchy. Same as @ref size().
inline int count() const {
return size();
}
Expand Down
28 changes: 17 additions & 11 deletions doomsday/libdeng2/src/data/archive.cpp
Expand Up @@ -27,7 +27,8 @@ struct Archive::Instance
/// Source data provided at construction.
IByteArray const *source;

/// Index maps entry paths to their metadata. Created by concrete subclasses.
/// Index maps entry paths to their metadata. Created by concrete subclasses
/// but we have the ownership.
PathTree *index;

/// Contents of the archive have been modified.
Expand All @@ -36,6 +37,11 @@ struct Archive::Instance
Instance(Archive &a, IByteArray const *src) : self(a), source(src), index(0), modified(false)
{}

~Instance()
{
delete index;
}

void readEntry(Path const &path, IBlock &deserializedData) const
{
Entry const &entry = static_cast<Entry const &>(
Expand All @@ -56,12 +62,6 @@ struct Archive::Instance

self.readFromSource(entry, path, deserializedData);
}

static int addToNames(PathTree::Node &entry, void *ptr)
{
reinterpret_cast<Archive::Names *>(ptr)->insert(entry.name());
return 0; // continue
}
};

Archive::Archive() : d(new Instance(*this, 0))
Expand Down Expand Up @@ -121,8 +121,11 @@ dint Archive::listFiles(Archive::Names& names, Path const &folder) const
PathTree::Node const &parent = d->index->find(folder, PathTree::MatchFull | PathTree::NoLeaf);

// Traverse the parent's nodes.
d->index->traverse(PathTree::NoBranch | PathTree::MatchParent, &parent, PathTree::no_hash,
Archive::Instance::addToNames, &names);
for(PathTreeIterator<PathTree> iter(parent.children()); iter.hasNext(); )
{
PathTree::Node const &node = iter.next();
if(node.isLeaf()) names.insert(node.name());
}

return names.size();
}
Expand All @@ -137,8 +140,11 @@ dint Archive::listFolders(Archive::Names &names, Path const &folder) const
PathTree::Node const &parent = d->index->find(folder, PathTree::MatchFull | PathTree::NoLeaf);

// Traverse the parent's nodes.
d->index->traverse(PathTree::NoLeaf | PathTree::MatchParent, &parent, PathTree::no_hash,
Archive::Instance::addToNames, &names);
for(PathTreeIterator<PathTree> iter(parent.children()); iter.hasNext(); )
{
PathTree::Node const &node = iter.next();
if(node.isBranch()) names.insert(node.name());
}

return names.size();
}
Expand Down
38 changes: 30 additions & 8 deletions doomsday/libdeng2/src/data/pathtree.cpp
Expand Up @@ -170,6 +170,7 @@ struct PathTree::Instance
// This is the leaf node we're looking for.
if(compFlags.testFlag(RelinquishMatching))
{
node->parent().removeChild(*node);
hash.erase(i);
}
return node;
Expand Down Expand Up @@ -271,6 +272,11 @@ bool PathTree::empty() const
return size() == 0;
}

PathTree::Flags PathTree::flags() const
{
return d->flags;
}

void PathTree::clear()
{
d->clear();
Expand Down Expand Up @@ -350,7 +356,8 @@ int PathTree::findAllPaths(FoundPaths &found, ComparisonFlags flags, QChar separ
}

static int iteratePathsInHash(PathTree const &pathTree, Path::hash_type hashKey,
PathTree::NodeType type, int flags, PathTree::Node const *parent,
PathTree::NodeType type, PathTree::ComparisonFlags flags,
PathTree::Node const *parent,
int (*callback) (PathTree::Node &, void *), void *parameters)
{
int result = 0;
Expand All @@ -360,16 +367,16 @@ static int iteratePathsInHash(PathTree const &pathTree, Path::hash_type hashKey,
throw Error("PathTree::iteratePathsInHash", String("Invalid hash %1, valid range is [0..%2).").arg(hashKey).arg(Path::hash_range-1));
}

PathTree::Nodes const &nodes = pathTree.nodes(type);
PathTree::Nodes const *nodes = &pathTree.nodes(type);

// Are we iterating nodes with a known hash?
if(hashKey != PathTree::no_hash)
{
// Yes.
PathTree::Nodes::const_iterator i = nodes.constFind(hashKey);
for(; i != nodes.end() && i.key() == hashKey; ++i)
PathTree::Nodes::const_iterator i = nodes->constFind(hashKey);
for(; i != nodes->end() && i.key() == hashKey; ++i)
{
if(!((flags & PathTree::MatchParent) && parent != &(*i)->parent()))
if(!(flags.testFlag(PathTree::MatchParent) && parent != &(*i)->parent()))
{
result = callback(**i, parameters);
if(result) break;
Expand All @@ -378,10 +385,25 @@ static int iteratePathsInHash(PathTree const &pathTree, Path::hash_type hashKey,
}
else
{
// No - iterate all nodes.
DENG2_FOR_EACH_CONST(PathTree::Nodes, i, nodes)
// No known hash, but if the parent is known, we can narrow our search
// to all the parent's children.
if(!pathTree.flags().testFlag(PathTree::NoLocalBranchIndex) &&
flags.testFlag(PathTree::MatchParent) && parent)
{
if(!((flags & PathTree::MatchParent) && parent != &(*i)->parent()))
nodes = &parent->children();
}

// No known hash -- iterate all potential nodes.
DENG2_FOR_EACH_CONST(PathTree::Nodes, i, *nodes)
{
if(flags.testFlag(PathTree::MatchParent) && (*i)->type() != type)
{
// Wrong kind of node -- branches could maintain separate indexes
// for branches and leaves...
continue;
}

if(!(flags.testFlag(PathTree::MatchParent) && parent != &(*i)->parent()))
{
result = callback(**i, parameters);
if(result) break;
Expand Down
62 changes: 49 additions & 13 deletions doomsday/libdeng2/src/data/pathtreenode.cpp
Expand Up @@ -25,28 +25,38 @@ namespace de {

struct PathTree::Node::Instance
{
/// @c true = this is a leaf node.
bool isLeaf;

/// PathTree which owns this node.
PathTree &tree;

/// Parent node in the user's logical hierarchy.
PathTree::Node *parent;

/// @c NULL for leaves, index of children for branches.
PathTree::Node::Children *children;

/// Unique identifier for the path fragment this node represents,
/// in the owning PathTree.
PathTree::SegmentId segmentId;

/// Parent node in the user's logical hierarchy.
PathTree::Node *parent;

Instance(PathTree &_tree, bool _isLeaf, PathTree::SegmentId _segmentId,
Instance(PathTree &_tree, bool isLeaf, PathTree::SegmentId _segmentId,
PathTree::Node *_parent)
: isLeaf(_isLeaf), tree(_tree), segmentId(_segmentId), parent(_parent)
{}
: tree(_tree), parent(_parent), children(0), segmentId(_segmentId)
{
if(!isLeaf) children = new PathTree::Node::Children;
}

~Instance()
{
delete children;
}
};

PathTree::Node::Node(PathTree::NodeArgs const &args)
{
d = new Instance(args.tree, args.type == PathTree::Leaf, args.segmentId, args.parent);

// Let the parent know of the new child node.
if(d->parent) d->parent->addChild(*this);
}

PathTree::Node::~Node()
Expand All @@ -56,7 +66,7 @@ PathTree::Node::~Node()

bool PathTree::Node::isLeaf() const
{
return d->isLeaf;
return d->children == 0;
}

PathTree &PathTree::Node::tree() const
Expand All @@ -69,6 +79,12 @@ PathTree::Node &PathTree::Node::parent() const
return *d->parent;
}

const PathTree::Nodes &PathTree::Node::children() const
{
DENG2_ASSERT(d->children != 0);
return *d->children;
}

bool PathTree::Node::isAtRootLevel() const
{
return d->parent == &d->tree.rootBranch();
Expand All @@ -79,6 +95,26 @@ PathTree::SegmentId PathTree::Node::segmentId() const
return d->segmentId;
}

void PathTree::Node::addChild(PathTree::Node &node)
{
if(!d->tree.flags().testFlag(PathTree::NoLocalBranchIndex))
{
DENG2_ASSERT(d->children != 0);

d->children->insert(node.hash(), &node);
}
}

void PathTree::Node::removeChild(PathTree::Node &node)
{
if(!d->tree.flags().testFlag(PathTree::NoLocalBranchIndex))
{
DENG2_ASSERT(d->children != 0);

d->children->remove(node.hash(), &node);
}
}

String const &PathTree::Node::name() const
{
return tree().segmentName(d->segmentId);
Expand Down Expand Up @@ -130,7 +166,7 @@ static int matchName(char const *string, char const *pattern)
int PathTree::Node::comparePath(de::Path const &searchPattern, ComparisonFlags flags) const
{
if(((flags & PathTree::NoLeaf) && isLeaf()) ||
((flags & PathTree::NoBranch) && !isLeaf()))
((flags & PathTree::NoBranch) && isBranch()))
return 1;

try
Expand Down Expand Up @@ -262,7 +298,7 @@ Path PathTree::Node::path(QChar sep) const
#endif

// Include a terminating path delimiter for branches.
if(!sep.isNull() && !isLeaf())
if(!sep.isNull() && isBranch())
{
parm.length++; // A single character.
}
Expand All @@ -271,7 +307,7 @@ Path PathTree::Node::path(QChar sep) const
pathConstructor(parm, *this);

// Terminating delimiter for branches.
if(!sep.isNull() && !isLeaf())
if(!sep.isNull() && isBranch())
{
parm.composedPath += sep;
}
Expand Down

0 comments on commit 7a224e9

Please sign in to comment.