optimize creation and sharing of effects

Implement equality test and hash for for property trees.  In an
Effect, make a hash table of Effects that inherit from it keyed on
their unmerged property tree. Using that, makeEffect() should return a
single Effect for given property tree description. Animations may
change that in the future...
This commit is contained in:
Tim Moore
2009-11-13 14:59:29 +01:00
parent 54c4055af3
commit 5991195062
5 changed files with 283 additions and 19 deletions

View File

@@ -16,12 +16,15 @@
#include <sstream>
#include <iomanip>
#include <iterator>
#include <stdio.h>
#include <string.h>
#include <boost/algorithm/string/find_iterator.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/bind.hpp>
#include <boost/functional/hash.hpp>
#include <boost/range.hpp>
#include <simgear/math/SGMath.hxx>
@@ -2379,6 +2382,146 @@ std::istream& readFrom<SGVec4d>(std::istream& stream, SGVec4d& result)
}
return stream;
}
namespace
{
bool compareNodeValue(const SGPropertyNode& lhs, const SGPropertyNode& rhs)
{
props::Type ltype = lhs.getType();
props::Type rtype = rhs.getType();
if (ltype != rtype)
return false;
switch (ltype) {
case props::NONE:
return true;
case props::ALIAS:
return false; // XXX Should we look in aliases?
case props::BOOL:
return lhs.getValue<bool>() == rhs.getValue<bool>();
case props::INT:
return lhs.getValue<int>() == rhs.getValue<int>();
case props::LONG:
return lhs.getValue<long>() == rhs.getValue<long>();
case props::FLOAT:
return lhs.getValue<float>() == rhs.getValue<float>();
case props::DOUBLE:
return lhs.getValue<double>() == rhs.getValue<double>();
case props::STRING:
case props::UNSPECIFIED:
return !strcmp(lhs.getStringValue(), rhs.getStringValue());
case props::VEC3D:
return lhs.getValue<SGVec3d>() == rhs.getValue<SGVec3d>();
case props::VEC4D:
return lhs.getValue<SGVec4d>() == rhs.getValue<SGVec4d>();
default:
return false;
}
}
}
}
bool SGPropertyNode::compare(const SGPropertyNode& lhs,
const SGPropertyNode& rhs)
{
if (&lhs == &rhs)
return true;
int lhsChildren = lhs.nChildren();
int rhsChildren = rhs.nChildren();
if (lhsChildren != rhsChildren)
return false;
if (lhsChildren == 0)
return compareNodeValue(lhs, rhs);
for (size_t i = 0; i < lhs._children.size(); ++i) {
const SGPropertyNode* lchild = lhs._children[i];
const SGPropertyNode* rchild = rhs._children[i];
// I'm guessing that the nodes will usually be in the same
// order.
if (lchild->getIndex() != rchild->getIndex()
|| lchild->getNameString() != rchild->getNameString()) {
rchild = 0;
for (PropertyList::const_iterator itr = rhs._children.begin(),
end = rhs._children.end();
itr != end;
++itr)
if (lchild->getIndex() == (*itr)->getIndex()
&& lchild->getNameString() == (*itr)->getNameString()) {
rchild = *itr;
break;
}
if (!rchild)
return false;
}
if (!compare(*lchild, *rchild))
return false;
}
return true;
}
struct PropertyPlaceLess {
typedef bool result_type;
bool operator()(SGPropertyNode_ptr lhs, SGPropertyNode_ptr rhs) const
{
int comp = lhs->getNameString().compare(rhs->getNameString());
if (comp == 0)
return lhs->getIndex() < rhs->getIndex();
else
return comp < 0;
}
};
size_t hash_value(const SGPropertyNode& node)
{
using namespace boost;
if (node.nChildren() == 0) {
switch (node.getType()) {
case props::NONE:
return 0;
case props::BOOL:
return hash_value(node.getValue<bool>());
case props::INT:
return hash_value(node.getValue<int>());
case props::LONG:
return hash_value(node.getValue<long>());
case props::FLOAT:
return hash_value(node.getValue<float>());
case props::DOUBLE:
return hash_value(node.getValue<double>());
case props::STRING:
case props::UNSPECIFIED:
{
const char *val = node.getStringValue();
return hash_range(val, val + strlen(val));
}
case props::VEC3D:
{
const SGVec3d val = node.getValue<SGVec3d>();
return hash_range(&val[0], &val[3]);
}
case props::VEC4D:
{
const SGVec4d val = node.getValue<SGVec4d>();
return hash_range(&val[0], &val[4]);
}
case props::ALIAS: // XXX Should we look in aliases?
default:
return 0;
}
} else {
size_t seed = 0;
PropertyList children(node._children.begin(), node._children.end());
sort(children.begin(), children.end(), PropertyPlaceLess());
for (PropertyList::const_iterator itr = children.begin(),
end = children.end();
itr != end;
++itr) {
hash_combine(seed, (*itr)->_name);
hash_combine(seed, (*itr)->_index);
hash_combine(seed, hash_value(**itr));
}
return seed;
}
}
// end of props.cxx

View File

@@ -1599,6 +1599,16 @@ public:
*/
void clearValue ();
/**
* Compare two property trees. The property trees are equal if: 1)
* They have no children, and have the same type and the values are
* equal, or 2) have the same number of children, and the
* corresponding children in each tree are equal. "corresponding"
* means have the same name and index.
*
* Attributes, removed children, and aliases aren't considered.
*/
static bool compare (const SGPropertyNode& lhs, const SGPropertyNode& rhs);
protected:
@@ -1615,7 +1625,7 @@ protected:
private:
// Get the raw value
// Get the raw value
bool get_bool () const;
int get_int () const;
long get_long () const;
@@ -1623,7 +1633,7 @@ private:
double get_double () const;
const char * get_string () const;
// Set the raw value
// Set the raw value
bool set_bool (bool value);
bool set_int (int value);
bool set_long (long value);
@@ -1672,7 +1682,7 @@ private:
bool _tied;
int _attr;
// The right kind of pointer...
// The right kind of pointer...
union {
SGPropertyNode * alias;
SGRaw* val;
@@ -1691,8 +1701,8 @@ private:
/**
* Register/unregister node that links to this node in its path cache.
*/
* Register/unregister node that links to this node in its path cache.
*/
void add_linked_node (hash_table * node) { _linkedNodes.push_back(node); }
bool remove_linked_node (hash_table * node);
@@ -1759,6 +1769,8 @@ private:
template<typename SplitItr>
friend SGPropertyNode* find_node_aux(SGPropertyNode * current, SplitItr& itr,
bool create, int last_index);
// For boost
friend size_t hash_value(const SGPropertyNode& node);
};
// Convenience functions for use in templates
@@ -1947,6 +1959,49 @@ inline SGPropertyNode* makeNode(SGPropertyNode* parent, const StringType& name)
return parent->getNode(name, true);
}
}
// For boost::hash
size_t hash_value(const SGPropertyNode& node);
// Helper comparison and hash functions for common cases
namespace simgear
{
namespace props
{
struct Compare
{
bool operator()(const SGPropertyNode* lhs, const SGPropertyNode* rhs) const
{
return SGPropertyNode::compare(*lhs, *rhs);
}
bool operator()(SGPropertyNode_ptr lhs, const SGPropertyNode* rhs) const
{
return SGPropertyNode::compare(*lhs, *rhs);
}
bool operator()(const SGPropertyNode* lhs, SGPropertyNode_ptr rhs) const
{
return SGPropertyNode::compare(*lhs, *rhs);
}
bool operator()(SGPropertyNode_ptr lhs, SGPropertyNode_ptr rhs) const
{
return SGPropertyNode::compare(*lhs, *rhs);
}
};
struct Hash
{
size_t operator()(const SGPropertyNode* node) const
{
return hash_value(*node);
}
size_t operator()(SGPropertyNode_ptr node) const
{
return hash_value(*node);
}
};
}
}
#endif // __PROPS_HXX
// end of props.hxx

View File

@@ -78,11 +78,13 @@ using namespace osgUtil;
using namespace effect;
Effect::Effect()
: _cache(0), _isRealized(false)
{
}
Effect::Effect(const Effect& rhs, const CopyOp& copyop)
: root(rhs.root), parametersProp(rhs.parametersProp)
: root(rhs.root), parametersProp(rhs.parametersProp), _cache(0),
_isRealized(rhs._isRealized)
{
typedef vector<ref_ptr<Technique> > TechniqueList;
for (TechniqueList::const_iterator itr = rhs.techniques.begin(),
@@ -132,6 +134,7 @@ void Effect::releaseGLObjects(osg::State* state) const
Effect::~Effect()
{
delete _cache;
}
void buildPass(Effect* effect, Technique* tniq, const SGPropertyNode* prop,
@@ -867,11 +870,14 @@ bool makeParametersFromStateSet(SGPropertyNode* effectRoot, const StateSet* ss)
// passes.
bool Effect::realizeTechniques(const osgDB::ReaderWriter::Options* options)
{
if (_isRealized)
return true;
PropertyList tniqList = root->getChildren("technique");
for (PropertyList::iterator itr = tniqList.begin(), e = tniqList.end();
itr != e;
++itr)
buildTechnique(this, *itr, options);
_isRealized = true;
return true;
}
@@ -895,6 +901,23 @@ void Effect::InitializeCallback::doUpdate(osg::Node* node, osg::NodeVisitor* nv)
}
}
bool Effect::Key::EqualTo::operator()(const Effect::Key& lhs,
const Effect::Key& rhs) const
{
if (lhs.paths.size() != rhs.paths.size()
|| !equal(lhs.paths.begin(), lhs.paths.end(), rhs.paths.begin()))
return false;
return props::Compare()(lhs.unmerged, rhs.unmerged);
}
size_t hash_value(const Effect::Key& key)
{
size_t seed = 0;
boost::hash_combine(seed, *key.unmerged);
boost::hash_range(seed, key.paths.begin(), key.paths.end());
return seed;
}
bool Effect_writeLocalData(const Object& obj, osgDB::Output& fw)
{
const Effect& effect = static_cast<const Effect&>(obj);

View File

@@ -20,6 +20,8 @@
#include <vector>
#include <string>
#include <boost/unordered_map.hpp>
#include <osg/Object>
#include <osgDB/ReaderWriter>
@@ -104,11 +106,43 @@ public:
{
void doUpdate(osg::Node* node, osg::NodeVisitor* nv);
};
protected:
std::vector<SGSharedPtr<Updater> > _extraData;
~Effect();
// Support for a cache of effects that inherit from this one, so
// Effect objects with the same parameters and techniques can be
// shared.
struct Key
{
Key(SGPropertyNode* unmerged_, const osgDB::FilePathList& paths_)
: unmerged(unmerged_), paths(paths_)
{
}
const SGPropertyNode_ptr unmerged;
const osgDB::FilePathList paths;
struct EqualTo
{
bool operator()(const Key& lhs, const Key& rhs) const;
};
};
typedef boost::unordered_map<Key, osg::ref_ptr<Effect>, boost::hash<Key>,
Key::EqualTo> Cache;
Cache* getCache()
{
if (!_cache)
_cache = new Cache;
return _cache;
}
Cache* _cache;
friend size_t hash_value(const Key& key);
friend Effect* makeEffect(SGPropertyNode* prop, bool realizeTechniques,
const osgDB::ReaderWriter::Options* options);
bool _isRealized;
};
// Automatic support for boost hash function
size_t hash_value(const Effect::Key&);
Effect* makeEffect(const std::string& name,
bool realizeTechniques,
const osgDB::ReaderWriter::Options* options = 0);

View File

@@ -165,23 +165,32 @@ Effect* makeEffect(SGPropertyNode* prop,
}
}
}
Effect* effect = new Effect;
Effect* effect = 0;
// Merge with the parent effect, if any
const SGPropertyNode* inheritProp = prop->getChild("inherits-from");
SGPropertyNode_ptr inheritProp = prop->getChild("inherits-from");
Effect* parent = 0;
if (inheritProp) {
parent = makeEffect(inheritProp->getStringValue(), false,
options);
if(parent)
{
effect->root = new SGPropertyNode;
mergePropertyTrees(effect->root, prop, parent->root);
effect->root->removeChild("inherits-from");
//prop->removeChild("inherits-from");
parent = makeEffect(inheritProp->getStringValue(), false, options);
if (parent) {
Effect::Cache* cache = parent->getCache();
Effect::Key key(prop, options->getDatabasePathList());
Effect::Cache::iterator itr = cache->find(key);
if (itr != cache->end()) {
effect = itr->second.get();
} else {
effect = new Effect;
effect->root = new SGPropertyNode;
mergePropertyTrees(effect->root, prop, parent->root);
cache->insert(make_pair(key, effect));
}
} else {
effect->root = prop;
effect->root->removeChild("inherits-from");
SG_LOG(SG_INPUT, SG_WARN, "can't find base effect " <<
inheritProp->getStringValue());
}
} else {
}
if (!effect) {
effect = new Effect;
effect->root = prop;
}
effect->parametersProp = effect->root->getChild("parameters");