/
ThreeWayMergeOperation.cpp
304 lines (254 loc) · 12.1 KB
/
ThreeWayMergeOperation.cpp
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
#include "ThreeWayMergeOperation.h"
#include "itextstream.h"
#include "NodeUtils.h"
namespace scene
{
namespace merge
{
ThreeWayMergeOperation::ThreeWayMergeOperation(const scene::IMapRootNodePtr& baseRoot,
const scene::IMapRootNodePtr& sourceRoot, const scene::IMapRootNodePtr& targetRoot) :
_baseRoot(baseRoot),
_sourceRoot(sourceRoot),
_targetRoot(targetRoot)
{}
ThreeWayMergeOperation::~ThreeWayMergeOperation()
{
// Clear the actions held by the base class before the root nodes are cleared
clearActions();
}
std::list<ComparisonResult::KeyValueDifference>::const_iterator ThreeWayMergeOperation::FindTargetDiffByKey(
const std::list<ComparisonResult::KeyValueDifference>& targetKeyValueDiffs, const std::string& key)
{
return std::find_if(targetKeyValueDiffs.begin(), targetKeyValueDiffs.end(),
[&](const ComparisonResult::KeyValueDifference& diff)
{
return string::iequals(diff.key, key);
});
}
bool ThreeWayMergeOperation::KeyValueDiffHasConflicts(const ComparisonResult::KeyValueDifference& sourceKeyValueDiff,
const ComparisonResult::KeyValueDifference& targetKeyValueDiff)
{
assert(string::iequals(targetKeyValueDiff.key, sourceKeyValueDiff.key));
// Key is matching, there's still a chance that this is not a conflict
switch (targetKeyValueDiff.type)
{
// If both are removing the key, that's fine
case ComparisonResult::KeyValueDifference::Type::KeyValueRemoved:
return targetKeyValueDiff.type != sourceKeyValueDiff.type;
// On key value change or add, the value must be the same to not conflict
case ComparisonResult::KeyValueDifference::Type::KeyValueAdded:
case ComparisonResult::KeyValueDifference::Type::KeyValueChanged:
return sourceKeyValueDiff.type == ComparisonResult::KeyValueDifference::Type::KeyValueRemoved ||
sourceKeyValueDiff.value != targetKeyValueDiff.value;
}
throw std::logic_error("Unhandled key value diff type in ThreeWayMergeOperation::KeyValueDiffHasConflicts");
}
void ThreeWayMergeOperation::processEntityModification(const ComparisonResult::EntityDifference& sourceDiff,
const ComparisonResult::EntityDifference& targetDiff)
{
assert(sourceDiff.type == ComparisonResult::EntityDifference::Type::EntityPresentButDifferent);
if (targetDiff.type == ComparisonResult::EntityDifference::Type::EntityMissingInBase)
{
// The target cannot possibly add this entity when in the source diff it's present in the base
throw std::logic_error("Entity " + sourceDiff.entityName + " is marked as modified in source, but as added in the target.");
}
if (targetDiff.type == ComparisonResult::EntityDifference::Type::EntityMissingInSource)
{
// This is a conflicting change, the source modified it, the target removed it
// When the user chooses to import the change, it will be an AddEntity action
addAction(std::make_shared<EntityConflictResolutionAction>(
targetDiff.sourceNode,
std::make_shared<AddEntityAction>(sourceDiff.sourceNode, _targetRoot)
));
return;
}
// Both graphs modified this entity, do an in-depth comparison
auto targetChildren = NodeUtils::CollectPrimitiveFingerprints(targetDiff.sourceNode);
// Every primitive change that has been done to the target map can be applied
// to the source map, since we can't detect whether one of them has been moved or retextured
for (const auto& primitiveDiff : sourceDiff.differingChildren)
{
bool primitivePresentInTargetMap = targetChildren.count(primitiveDiff.fingerprint) != 0;
switch (primitiveDiff.type)
{
case ComparisonResult::PrimitiveDifference::Type::PrimitiveAdded:
{
// Add this primitive if it isn't there already
if (!primitivePresentInTargetMap)
{
addAction(std::make_shared<AddChildAction>(primitiveDiff.node, targetDiff.sourceNode));
}
break;
}
case ComparisonResult::PrimitiveDifference::Type::PrimitiveRemoved:
// Check if this primitive is still present in the target map, otherwise we can't remove it
if (primitivePresentInTargetMap)
{
addAction(std::make_shared<RemoveChildAction>(targetChildren[primitiveDiff.fingerprint]));
}
break;
}
}
// The key value changes can be applied only if they are not targeting the same key
// unless the change has actually the same outcome
for (const auto& sourceKeyValueDiff : sourceDiff.differingKeyValues)
{
auto targetKeyValueDiff = FindTargetDiffByKey(targetDiff.differingKeyValues, sourceKeyValueDiff.key);
if (targetKeyValueDiff == targetDiff.differingKeyValues.end())
{
// Not a key that changed in the target, accept this change
addActionForKeyValueDiff(sourceKeyValueDiff, targetDiff.sourceNode);
continue;
}
// Ignore NOP changes, when the target obviously made the same change
if (sourceKeyValueDiff == *targetKeyValueDiff)
{
continue;
}
// Check if this key change is conflicting with the target change
if (!KeyValueDiffHasConflicts(sourceKeyValueDiff, *targetKeyValueDiff))
{
// Accept this change
addActionForKeyValueDiff(sourceKeyValueDiff, targetDiff.sourceNode);
}
else
{
// Create a conflict resolution action for this key value change
addAction(std::make_shared<EntityKeyValueConflictResolutionAction>(
targetDiff.sourceNode, // conflicting entity
createActionForKeyValueDiff(sourceKeyValueDiff, targetDiff.sourceNode), // conflicting source change
createActionForKeyValueDiff(*targetKeyValueDiff, targetDiff.sourceNode) // conflicting target change
));
}
}
}
void ThreeWayMergeOperation::processEntityDifferences(const std::list<ComparisonResult::EntityDifference>& sourceDiffs,
const std::list<ComparisonResult::EntityDifference>& targetDiffs)
{
// Create source and target entity diff dictionaries (by entity name)
for (auto it = sourceDiffs.begin(); it != sourceDiffs.end(); ++it)
{
_sourceDifferences[it->entityName] = it;
}
for (auto it = targetDiffs.begin(); it != targetDiffs.end(); ++it)
{
_targetDifferences[it->entityName] = it;
}
// Collect a map of all target entities for faster lookup later
_targetRoot->foreachNode([&](const INodePtr& candidate)
{
if (candidate->getNodeType() == INode::Type::Entity)
{
_targetEntities.emplace(NodeUtils::GetEntityName(candidate), candidate);
}
return true;
});
// Check each entity difference from the base to the source map
// fully accept only those entities that are not altered in the target map, and detect conflicts
for (const auto& pair : _sourceDifferences)
{
auto targetDiff = _targetDifferences.find(pair.first);
if (targetDiff == _targetDifferences.end())
{
// Change is targeting an entity that has not been altered in the source map => accept
switch (pair.second->type)
{
case ComparisonResult::EntityDifference::Type::EntityMissingInSource:
{
auto entityToRemove = findTargetEntityByName(pair.first);
assert(entityToRemove);
addAction(std::make_shared<RemoveEntityAction>(entityToRemove));
}
break;
case ComparisonResult::EntityDifference::Type::EntityMissingInBase:
addAction(std::make_shared<AddEntityAction>(pair.second->sourceNode, _targetRoot));
break;
case ComparisonResult::EntityDifference::Type::EntityPresentButDifferent:
{
auto entityToModify = findTargetEntityByName(pair.first);
assert(entityToModify);
for (const auto& keyValueDiff : pair.second->differingKeyValues)
{
addActionForKeyValueDiff(keyValueDiff, entityToModify);
}
for (const auto& primitiveDiff : pair.second->differingChildren)
{
addActionsForPrimitiveDiff(primitiveDiff, entityToModify);
}
}
break;
};
continue;
}
// Check diff type (entity add/remove)
switch (pair.second->type)
{
case ComparisonResult::EntityDifference::Type::EntityMissingInBase: // entity was added to source
if (targetDiff->second->type == ComparisonResult::EntityDifference::Type::EntityMissingInSource ||
targetDiff->second->type == ComparisonResult::EntityDifference::Type::EntityPresentButDifferent)
{
// The target cannot remove or modify an entity that has been marked as added in the source diff
throw std::logic_error("Error " + pair.first + " was marked as added in source, but removed/modified in target");
}
// Both graphs had this entity added, mark this for inclusion
// unless it turns out this added entity in the source is 100% the same as in the target
if (pair.second->sourceFingerprint != targetDiff->second->sourceFingerprint)
{
addAction(std::make_shared<AddEntityAction>(pair.second->sourceNode, _targetRoot));
}
break;
case ComparisonResult::EntityDifference::Type::EntityMissingInSource: // entity was removed in source
if (targetDiff->second->type == ComparisonResult::EntityDifference::Type::EntityMissingInBase)
{
// The target cannot add an entity that has been marked as removed in the source diff, it was already there
throw std::logic_error("Error " + pair.first + " was marked as removed in source, but as added in target");
}
if (targetDiff->second->type == ComparisonResult::EntityDifference::Type::EntityMissingInSource)
{
// Entity is gone in the target too, nothing to do here
continue;
}
// Entity has been removed in source, but modified in target, this is a conflict
addAction(std::make_shared<EntityConflictResolutionAction>(
targetDiff->second->sourceNode, // conflicting entity
std::make_shared<RemoveEntityAction>(targetDiff->second->sourceNode) // conflicting change
));
break;
case ComparisonResult::EntityDifference::Type::EntityPresentButDifferent:
// This entity has been modified in the source, check the target diff
processEntityModification(*pair.second, *targetDiff->second);
break;
}
}
// Cleanup temporary data
_sourceDifferences.clear();
_targetDifferences.clear();
_targetEntities.clear();
}
ThreeWayMergeOperation::Ptr ThreeWayMergeOperation::CreateFromComparisonResults(
const ComparisonResult& baseToSource, const ComparisonResult& baseToTarget)
{
if (baseToSource.getBaseRootNode() != baseToTarget.getBaseRootNode())
{
throw std::runtime_error("The base scene of the two comparison results must be the same");
}
auto operation = std::make_shared<ThreeWayMergeOperation>(baseToSource.getBaseRootNode(),
baseToSource.getSourceRootNode(), baseToTarget.getSourceRootNode());
operation->processEntityDifferences(baseToSource.differingEntities, baseToTarget.differingEntities);
return operation;
}
void ThreeWayMergeOperation::setMergeSelectionGroups(bool enabled)
{
// TODO
}
void ThreeWayMergeOperation::setMergeLayers(bool enabled)
{
// TODO
}
INodePtr ThreeWayMergeOperation::findTargetEntityByName(const std::string& name)
{
auto found = _targetEntities.find(name);
return found != _targetEntities.end() ? found->second : INodePtr();
}
}
}