/
ThreeWayMergeOperation.cpp
424 lines (352 loc) · 17 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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
#include "ThreeWayMergeOperation.h"
#include "itextstream.h"
#include "inamespace.h"
#include "NodeUtils.h"
#include "GraphComparer.h"
#include "ThreeWaySelectionGroupMerger.h"
#include "ThreeWayLayerMerger.h"
namespace scene
{
namespace merge
{
// Contains lookup tables needed during analysis of the two scenes
struct ThreeWayMergeOperation::ComparisonData
{
std::map<std::string, std::list<ComparisonResult::EntityDifference>::const_iterator> sourceDifferences;
std::map<std::string, std::list<ComparisonResult::EntityDifference>::const_iterator> targetDifferences;
std::map<std::string, INodePtr> targetEntities;
ComparisonResult::Ptr baseToSource;
ComparisonResult::Ptr baseToTarget;
ComparisonData(const IMapRootNodePtr& baseRoot, const IMapRootNodePtr& sourceRoot, const IMapRootNodePtr& targetRoot)
{
baseToSource = GraphComparer::Compare(sourceRoot, baseRoot);
baseToTarget = GraphComparer::Compare(targetRoot, baseRoot);
// Create source and target entity diff dictionaries (by entity name)
for (auto it = baseToSource->differingEntities.begin(); it != baseToSource->differingEntities.end(); ++it)
{
sourceDifferences[it->entityName] = it;
}
for (auto it = baseToTarget->differingEntities.begin(); it != baseToTarget->differingEntities.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;
});
}
INodePtr findTargetEntityByName(const std::string& name) const
{
auto found = targetEntities.find(name);
return found != targetEntities.end() ? found->second : INodePtr();
}
};
ThreeWayMergeOperation::ThreeWayMergeOperation(const scene::IMapRootNodePtr& baseRoot,
const scene::IMapRootNodePtr& sourceRoot, const scene::IMapRootNodePtr& targetRoot) :
_baseRoot(baseRoot),
_sourceRoot(sourceRoot),
_targetRoot(targetRoot),
_mergeSelectionGroups(true),
_mergeLayers(true)
{}
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);
});
}
ConflictType ThreeWayMergeOperation::GetKeyValueConflictType(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)
{
case ComparisonResult::KeyValueDifference::Type::KeyValueRemoved:
{
// Target had the key removed
if (sourceKeyValueDiff.type == ComparisonResult::KeyValueDifference::Type::KeyValueAdded)
{
throw std::logic_error("Source cannot add a key that has been removed in target, as it was present in base.");
}
// If both are removing the key, that's fine, otherwise it's a conflict
return sourceKeyValueDiff.type == ComparisonResult::KeyValueDifference::Type::KeyValueChanged ?
ConflictType::ModificationOfRemovedKeyValue : ConflictType::NoConflict;
}
// On key value change or add, the value must be the same to not conflict
case ComparisonResult::KeyValueDifference::Type::KeyValueAdded:
{
if (sourceKeyValueDiff.type != ComparisonResult::KeyValueDifference::Type::KeyValueAdded)
{
throw std::logic_error("Source cannot remove or modify a key that has been added in target, as it was present in base.");
}
// Value must match
return sourceKeyValueDiff.value != targetKeyValueDiff.value ? ConflictType::SettingKeyToDifferentValue : ConflictType::NoConflict;
}
case ComparisonResult::KeyValueDifference::Type::KeyValueChanged:
{
if (sourceKeyValueDiff.type == ComparisonResult::KeyValueDifference::Type::KeyValueAdded)
{
throw std::logic_error("Source cannot add a key that has been modified in target, as it was present in base.");
}
if (sourceKeyValueDiff.type == ComparisonResult::KeyValueDifference::Type::KeyValueRemoved)
{
return ConflictType::RemovalOfModifiedKeyValue;
}
// Both maps changes this value, compare the strings
return sourceKeyValueDiff.value != targetKeyValueDiff.value ? ConflictType::SettingKeyToDifferentValue : ConflictType::NoConflict;
}
}
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>(
ConflictType::ModificationOfRemovedEntity,
sourceDiff.sourceNode, // the conflicting source entity
INodePtr(), // the target entity (is no longer present)
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
auto conflictType = GetKeyValueConflictType(sourceKeyValueDiff, *targetKeyValueDiff);
if (conflictType == ConflictType::NoConflict)
{
// Accept this change
addActionForKeyValueDiff(sourceKeyValueDiff, targetDiff.sourceNode);
}
else
{
// Create a conflict resolution action for this key value change
addAction(std::make_shared<EntityKeyValueConflictResolutionAction>(
conflictType,
sourceDiff.sourceNode, // conflicting source entity
targetDiff.sourceNode, // conflicting target entity
createActionForKeyValueDiff(sourceKeyValueDiff, targetDiff.sourceNode), // conflicting source change
createActionForKeyValueDiff(*targetKeyValueDiff, targetDiff.sourceNode) // conflicting target change
));
}
}
}
void ThreeWayMergeOperation::compareAndCreateActions()
{
ComparisonData data(_baseRoot, _sourceRoot, _targetRoot);
// 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 : data.sourceDifferences)
{
auto targetDiff = data.targetDifferences.find(pair.first);
if (targetDiff == data.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 = data.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 = data.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>(
ConflictType::RemovalOfModifiedEntity,
INodePtr(), // conflicting source entity (is not present anymore)
targetDiff->second->sourceNode, // conflicting target 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;
}
}
}
ThreeWayMergeOperation::Ptr ThreeWayMergeOperation::Create(const IMapRootNodePtr& baseRoot,
const IMapRootNodePtr& sourceRoot, const IMapRootNodePtr& targetRoot)
{
if (baseRoot == sourceRoot || baseRoot == targetRoot || sourceRoot == targetRoot)
{
throw std::runtime_error("None of the root nodes must be equal to any of the other two");
}
auto operation = std::make_shared<ThreeWayMergeOperation>(baseRoot, sourceRoot, targetRoot);
// Phase 1 is to detect any entity additions from the source to the target that might cause name conflicts
// After this pass some key values might have been changed
operation->adjustSourceEntitiesWithNameConflicts();
// Phase 2 will run another comparison of the graphs (since key values might have been modified)
operation->compareAndCreateActions();
return operation;
}
void ThreeWayMergeOperation::adjustSourceEntitiesWithNameConflicts()
{
ComparisonData data(_baseRoot, _sourceRoot, _targetRoot);
std::set<INodePtr> sourceEntitiesToBeRenamed;
// Check each entity difference from the base to the source map
for (const auto& pair : data.sourceDifferences)
{
if (pair.second->type != ComparisonResult::EntityDifference::Type::EntityMissingInBase)
{
continue; // we're only looking for additions
}
// Find an entity change in the target map that affects the same entity name
// These are the source entities we have to rename first
auto targetDiff = data.targetDifferences.find(pair.first);
if (targetDiff == data.targetDifferences.end() || targetDiff->second->type != pair.second->type)
{
continue; // no target diff or not a target addition
}
// There's a chance this target entity is exactly the same as in the source
if (targetDiff->second->sourceFingerprint == pair.second->sourceFingerprint)
{
continue; // The entity turns out to be the same
}
rMessage() << "Source entity needs to be renamed." << std::endl;
sourceEntitiesToBeRenamed.insert(pair.second->sourceNode);
}
rMessage() << "Got " << sourceEntitiesToBeRenamed.size() << " entities to be renamed in "
"the source before they can be imported in the target" << std::endl;
// Let the target namespace detect any conflicts and assign new names to the source nodes
// This might change a few key values across the source scene
_targetRoot->getNamespace()->ensureNoConflicts(_sourceRoot, sourceEntitiesToBeRenamed);
}
void ThreeWayMergeOperation::applyActions()
{
MergeOperationBase::applyActions();
if (_mergeSelectionGroups)
{
ThreeWaySelectionGroupMerger merger(_baseRoot, _sourceRoot, _targetRoot);
merger.adjustTargetGroups();
}
if (_mergeLayers)
{
ThreeWayLayerMerger merger(_baseRoot, _sourceRoot, _targetRoot);
merger.adjustTargetLayers();
}
}
void ThreeWayMergeOperation::setMergeSelectionGroups(bool enabled)
{
_mergeSelectionGroups = enabled;
}
void ThreeWayMergeOperation::setMergeLayers(bool enabled)
{
_mergeLayers = enabled;
}
}
}