1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
|
#include "Namespace.h"
#include "inameobserver.h"
#include "itextstream.h"
#include "module/StaticModule.h"
#include <list>
class ConnectNamespacedWalker :
public scene::NodeVisitor
{
Namespace* _nspace;
public:
ConnectNamespacedWalker(Namespace* nspace) :
_nspace(nspace)
{}
virtual bool pre(const scene::INodePtr& node)
{
NamespacedPtr namespaced = Node_getNamespaced(node);
if (!namespaced)
{
return true;
}
INamespace* foreignNamespace = namespaced->getNamespace();
// Do not reconnect to same namespace, this causes invalid name changes
if (foreignNamespace == _nspace)
{
rWarning() << "ConnectNamespacedWalker: node '" << node->name()
<< "' is already attached to namespace at " << _nspace
<< std::endl;
return true;
}
else if (foreignNamespace)
{
// The node is already connected to a different namespace, disconnect
namespaced->disconnectNameObservers();
namespaced->detachNames();
namespaced->setNamespace(NULL);
}
// Set the namespace reference and add all "names"
namespaced->setNamespace(_nspace);
namespaced->attachNames();
return true;
}
};
class DisconnectNamespacedWalker :
public scene::NodeVisitor
{
public:
virtual bool pre(const scene::INodePtr& node) {
NamespacedPtr namespaced = Node_getNamespaced(node);
// Only do this, if the item is Namespaced and attached to a Namespace
if (namespaced != NULL && namespaced->getNamespace() != NULL) {
// Remove names and clear namespace
namespaced->detachNames();
namespaced->setNamespace(NULL);
}
return true;
}
};
class ConnectNameObserverWalker :
public scene::NodeVisitor
{
public:
virtual bool pre(const scene::INodePtr& node) {
NamespacedPtr namespaced = Node_getNamespaced(node);
// Only do this, if the item is Namespaced and attached to a Namespace
if (namespaced != NULL && namespaced->getNamespace() != NULL) {
namespaced->connectNameObservers();
}
return true;
}
};
class DisconnectNameObserverWalker :
public scene::NodeVisitor
{
public:
virtual bool pre(const scene::INodePtr& node) {
NamespacedPtr namespaced = Node_getNamespaced(node);
// Only do this, if the item is Namespaced and attached to a Namespace
if (namespaced != NULL && namespaced->getNamespace() != NULL) {
namespaced->disconnectNameObservers();
}
return true;
}
};
// A walker importing all names into this namespace
struct GatherNamespacedWalker : public scene::NodeVisitor
{
// The set of imported names to keep track of what got imported already
std::set<NamespacedPtr> result;
virtual bool pre(const scene::INodePtr& node)
{
// Insert node if it is a Namespaced object
NamespacedPtr namespaced = Node_getNamespaced(node);
if (namespaced)
{
result.insert(namespaced);
}
return true;
}
};
void Namespace::connect(const scene::INodePtr& root)
{
// Now traverse the subgraph and connect the nodes
ConnectNamespacedWalker firstWalker(this);
root->traverse(firstWalker);
ConnectNameObserverWalker secondWalker;
root->traverse(secondWalker);
}
void Namespace::disconnect(const scene::INodePtr& root)
{
// First, disconnect all NameObservers
DisconnectNameObserverWalker firstWalker;
root->traverse(firstWalker);
// Second, remove all "names" from the namespace and clear the reference
DisconnectNamespacedWalker secondWalker;
root->traverse(secondWalker);
}
bool Namespace::nameExists(const std::string& name)
{
assert(!name.empty());
return _uniqueNames.nameExists(name);
}
bool Namespace::insert(const std::string& name) {
return _uniqueNames.insert(name);
}
bool Namespace::erase(const std::string& name) {
return _uniqueNames.erase(name);
}
std::string Namespace::addUniqueName(const std::string& originalName)
{
return _uniqueNames.insertUnique(originalName);
}
Namespace::~Namespace()
{
assert(_observers.empty());
}
void Namespace::addNameObserver(const std::string& name, NameObserver& observer) {
// Just insert the observer
_observers.insert(ObserverMap::value_type(name, &observer));
}
void Namespace::removeNameObserver(const std::string& name, NameObserver& observer) {
// Lookup the iterator boundaries and find the observer
for (ObserverMap::iterator i = _observers.lower_bound(name), upperBound = _observers.upper_bound(name);
i != _observers.end() && i != upperBound; i++)
{
if (i->second == &observer) {
_observers.erase(i);
break;
}
}
}
void Namespace::nameChanged(const std::string& oldName, const std::string& newName) {
// Check if we should do anything at all
if (oldName == newName) {
return;
}
// Remove the name from our UniqueNameSet
if (!_uniqueNames.erase(oldName)) {
rError() << "[Namespace]: Could not remove old name before rename: " << oldName << "\n";
}
// Insert the new name, the NameObservers expect the new name to be present in the namespace
_uniqueNames.insert(newName);
// Notify the observers
// greebo: Note, we compare the name after checking upper_bound() once more to catch cases
// where a name gets changed from path_2 to path_23 for instance, which could result in a new
// upper_bound(). The operator!= subsequently fails as the iterator points to the element
// *after* the new upper_bound(), and the wrong observer gets called.
// Performance should not be a problem as this only gets executed on name changes, and there
// is a finite number of observers watching a single name anyway.
for (ObserverMap::iterator i = _observers.lower_bound(oldName);
i != _observers.end() && i != _observers.upper_bound(oldName) && i->first == oldName;
/* in-loop increment */)
{
assert(i->second != NULL);
// Call the observer, but increment the iterator beforehand,
// as the observer might remove this one.
(i++)->second->onNameChange(oldName, newName);
}
// greebo: usually, there are no more observers left in the multimap at this point
// as the default observing classes remove themselves from the namespace when the
// name changes. However, it's possible that there are some observers left,
// so we need to redirect them to the new name.
// This list will temporarily hold observers, as we need to change
// their association after the name change.
std::list<NameObserver*> temp;
// Now go through that list again, and rename all remaining observers which
// point at the old name (ideally, there are none left at this point)
for (ObserverMap::iterator i = _observers.lower_bound(oldName);
i != _observers.end() && i != _observers.upper_bound(oldName);
/* in-loop increment */)
{
temp.push_back(i->second);
_observers.erase(i++);
}
// greebo: Now, associate each observer with the new name (it has changed after all)
for (std::list<NameObserver*>::iterator i = temp.begin(); i != temp.end(); i++)
{
_observers.insert(ObserverMap::value_type(newName, *i));
}
}
void Namespace::ensureNoConflicts(const scene::INodePtr& foreignRoot)
{
// Collect all namespaced items from the foreign root
GatherNamespacedWalker walker;
foreignRoot->traverse(walker);
ensureNoConflicts(foreignRoot, walker.result);
}
void Namespace::ensureNoConflicts(const scene::INodePtr& foreignRoot, const std::set<scene::INodePtr>& foreignNodes)
{
// Filter out all namespaced items from the given scene node list
std::set<NamespacedPtr> foreignItems;
for (const auto& node : foreignNodes)
{
auto namespaced = Node_getNamespaced(node);
if (namespaced)
{
foreignItems.emplace(std::move(namespaced));
}
}
ensureNoConflicts(foreignRoot, foreignItems);
}
void Namespace::ensureNoConflicts(const scene::INodePtr& foreignRoot, const std::set<NamespacedPtr>& foreignNodes)
{
// Instantiate a new, temporary namespace for the nodes below root
Namespace foreignNamespace;
// Move all nodes below (and including) root into this temporary namespace
foreignNamespace.connect(foreignRoot);
rDebug() << "Namespace::ensureNoConflicts(): importing set of "
<< foreignNodes.size() << " namespaced nodes" << std::endl;
// Build a union set containing all imported names and all existing names.
// We need to know all existing names to ensure that newly created names are
// unique in *both* namespaces
UniqueNameSet allNames = _uniqueNames;
allNames.merge(foreignNamespace._uniqueNames);
// Process each object in the to-be-imported tree of nodes, ensuring that it
// has a unique name
for (const auto& foreignNode : foreignNodes)
{
// If the imported node conflicts with a name in THIS namespace, then it
// needs to be given a new name which is unique in BOTH namespaces.
if (_uniqueNames.nameExists(foreignNode->getName()))
{
// Name exists in the target namespace, get a new name
std::string uniqueName = allNames.insertUnique(foreignNode->getName());
rMessage() << "Namespace::ensureNoConflicts(): '" << foreignNode->getName()
<< "' already exists in this namespace. Rename it to '"
<< uniqueName << "'\n";
// Change the name of the imported node, this should trigger all
// observers in the foreign namespace
foreignNode->changeName(uniqueName);
}
else
{
// Name does not exist yet, insert it into the local combined
// namespace (but not our destination namespace, this will be
// populated in the subsequent call to connect()).
allNames.insert(foreignNode->getName());
}
}
// at this point, all names in the foreign namespace have been converted to
// something unique in this namespace. The calling code can now move the
// nodes into this namespace without name conflicts
// Disconnect the root from the foreign namespace again, it will be destroyed now
foreignNamespace.disconnect(foreignRoot);
}
|