diff --git a/radiantcore/map/algorithm/SceneGraphComparer.cpp b/radiantcore/map/algorithm/SceneGraphComparer.cpp index c8953c8129..466dafb073 100644 --- a/radiantcore/map/algorithm/SceneGraphComparer.cpp +++ b/radiantcore/map/algorithm/SceneGraphComparer.cpp @@ -2,6 +2,7 @@ #include #include "icomparablenode.h" +#include "string/string.h" #include "command/ExecutionNotPossible.h" namespace map @@ -88,6 +89,7 @@ void SceneGraphComparer::processDifferingEntities(const EntityMismatchByName& so return left.first < right.first; }; + // Calculate intersection and two-way exclusives std::set_intersection(sourceMismatches.begin(), sourceMismatches.end(), targetMismatches.begin(), targetMismatches.end(), std::back_inserter(matchingByName), compareEntityNames); @@ -101,13 +103,19 @@ void SceneGraphComparer::processDifferingEntities(const EntityMismatchByName& so { rMessage() << " - EntityPresentButDifferent: " << match.first << std::endl; - _result->differingEntities.emplace_back(ComparisonResult::EntityDifference + auto& entityDiff = _result->differingEntities.emplace_back(ComparisonResult::EntityDifference { match.second.fingerPrint, match.second.node, match.second.entityName, ComparisonResult::EntityDifference::Type::EntityPresentButDifferent }); + + auto sourceNode = sourceMismatches.find(match.second.entityName)->second.node; + auto targetNode = targetMismatches.find(match.second.entityName)->second.node; + + // Analyse the key values + entityDiff.differingKeyValues = compareKeyValues(sourceNode, targetNode); } for (const auto& mismatch : missingInSource) @@ -137,6 +145,95 @@ void SceneGraphComparer::processDifferingEntities(const EntityMismatchByName& so } } +namespace +{ + using KeyValueMap = std::map; + + inline KeyValueMap loadKeyValues(const scene::INodePtr& entityNode) + { + KeyValueMap result; + + auto entity = Node_getEntity(entityNode); + + entity->forEachKeyValue([&](const std::string& key, const std::string& value) + { + result.emplace(key, value); + }, false); + + return result; + } +} + +std::list SceneGraphComparer::compareKeyValues( + const scene::INodePtr& sourceNode, const scene::INodePtr& targetNode) +{ + std::list result; + + auto sourceKeyValues = loadKeyValues(sourceNode); + auto targetKeyValues = loadKeyValues(targetNode); + + string::ILess icmp; + auto compareKeysNoCase = [&](const KeyValueMap::value_type& left, const KeyValueMap::value_type& right) + { + return icmp(left.first, right.first); + }; + + std::vector missingInSource; + std::vector missingInTarget; + std::vector presentInBoth; + + std::set_intersection(sourceKeyValues.begin(), sourceKeyValues.end(), + targetKeyValues.begin(), targetKeyValues.end(), std::back_inserter(presentInBoth), compareKeysNoCase); + std::set_difference(sourceKeyValues.begin(), sourceKeyValues.end(), + targetKeyValues.begin(), targetKeyValues.end(), std::back_inserter(missingInTarget), compareKeysNoCase); + std::set_difference(targetKeyValues.begin(), targetKeyValues.end(), + sourceKeyValues.begin(), sourceKeyValues.end(), std::back_inserter(missingInSource), compareKeysNoCase); + + for (const auto& pair : missingInTarget) + { + rMessage() << " - Key " << pair.first << " not present in target entity" << std::endl; + + result.emplace_back(ComparisonResult::KeyValueDifference + { + pair.first, + pair.second, + ComparisonResult::KeyValueDifference::Type::KeyValueAdded + }); + } + + for (const auto& pair : missingInSource) + { + rMessage() << " - Key " << pair.first << " not present in source entity" << std::endl; + + result.emplace_back(ComparisonResult::KeyValueDifference + { + pair.first, + pair.second, + ComparisonResult::KeyValueDifference::Type::KeyValueRemoved + }); + } + + // Compare each value which is present on both entities + for (const auto& pair : presentInBoth) + { + if (sourceKeyValues[pair.first] == targetKeyValues[pair.first]) + { + continue; + } + + rMessage() << " - Key " << pair.first << " changed in source to value " << pair.second << std::endl; + + result.emplace_back(ComparisonResult::KeyValueDifference + { + pair.first, + pair.second, + ComparisonResult::KeyValueDifference::Type::KeyValueChanged + }); + } + + return result; +} + SceneGraphComparer::Fingerprints SceneGraphComparer::collectEntityFingerprints(const scene::INodePtr& root) { Fingerprints result; diff --git a/radiantcore/map/algorithm/SceneGraphComparer.h b/radiantcore/map/algorithm/SceneGraphComparer.h index 90912f71ce..c2a896e3b9 100644 --- a/radiantcore/map/algorithm/SceneGraphComparer.h +++ b/radiantcore/map/algorithm/SceneGraphComparer.h @@ -25,6 +25,21 @@ struct ComparisonResult scene::INodePtr targetNode; }; + struct KeyValueDifference + { + std::string key; + std::string value; + + enum class Type + { + KeyValueAdded, // key is present on the source entity, but not on the target + KeyValueRemoved, // key is present on the target entity, but not on the source + KeyValueChanged, // key present on both, but value is different + }; + + Type type; + }; + struct EntityDifference { std::string fingerPrint; @@ -39,6 +54,8 @@ struct ComparisonResult }; Type type; + + std::list differingKeyValues; }; // The collection of entities with the same fingerprint value @@ -57,7 +74,6 @@ class SceneGraphComparer ComparisonResult::Ptr _result; using Fingerprints = std::map; - using FingerprintsByType = std::map; public: struct EntityMismatch @@ -87,6 +103,9 @@ class SceneGraphComparer void processDifferingEntities(const EntityMismatchByName& sourceMismatches, const EntityMismatchByName& targetMismatches); Fingerprints collectEntityFingerprints(const scene::INodePtr& root); + + std::list compareKeyValues( + const scene::INodePtr& sourceNode, const scene::INodePtr& targetNode); }; }