private void MapEntities <T>(AssetCompositeHierarchyData <EntityDesign, Entity> hierarchyData, Dictionary <GroupPartKey, T> entities, Guid?instancePartIdArg = null) where T : EntityEntry { if (hierarchyData == null) { return; } // Important, if the hierarchy is coming from a part, we need to clone it entirely // and associate correctly the instancePartId with each entities that it is composed of. if (instancePartIdArg.HasValue) { hierarchyData = (AssetCompositeHierarchyData <EntityDesign, Entity>)AssetCloner.Clone(hierarchyData); } foreach (var entityDesign in hierarchyData.Parts) { if (instancePartIdArg.HasValue) { entityDesign.BasePartInstanceId = instancePartIdArg; } var key = new GroupPartKey(instancePartIdArg, entityDesign.Entity.Id); if (entities.ContainsKey(key)) { continue; } EntityEntry remap; if (typeof(T) == typeof(NewEntityEntry)) { remap = new NewEntityEntry(entityDesign, hierarchyData, entities.Count); } else if (typeof(T) == typeof(BaseEntityEntry)) { remap = new BaseEntityEntry(entityDesign, hierarchyData, entities.Count); } else { throw new ArgumentException($"Invalid type [{typeof(T)}]. Expecting concrete type NewEntityEntry or BaseEntityEntry"); } entities[key] = (T)remap; } }
/// <summary> /// Prepare the merge by computing internal dictionaries /// </summary> private void PrepareMerge() { // Process NewAsset MapEntities(newAsset.Hierarchy, newEntities); // Process BaseAsset MapEntities(baseAsset?.Hierarchy, baseEntities); if (newAsset.BaseParts != null) { foreach (var partItem in newAsset.GetBasePartInstanceIds()) { foreach (var groupPartId in partItem.Value) { // Because we have a groupPartId, it will clone the hierarchy so that // we can safely merge instances later with NewEntity MapEntities(partItem.Key.Hierarchy, baseEntities, groupPartId); } } } // Process NewBaseAsset MapEntities(newBaseAsset?.Hierarchy, newBaseEntities); if (newBaseParts != null) { foreach (var partItem in newAsset.GetBasePartInstanceIds(newBaseParts)) { foreach (var groupPartId in partItem.Value) { // Because we have a groupPartId, it will clone the hierarchy so that // we can safely merge instances later with NewEntity MapEntities(partItem.Key.Hierarchy, newBaseEntities, groupPartId); } } } // Compute Entities Added by newbase (not present in base) foreach (var entityFromNewBase in newBaseEntities) { var entityId = entityFromNewBase.Value.EntityDesign.Entity.Id; // For PartInstanceId key, we take it from the entityFromNewBase.Key var basePartInstanceId = entityFromNewBase.Key.PartInstanceId; var key = new GroupPartKey(basePartInstanceId, entityId); if (!baseEntities.ContainsKey(key)) { var tempHiearchy = new AssetCompositeHierarchyData <EntityDesign, Entity>(); tempHiearchy.Parts.Add(entityFromNewBase.Value.EntityDesign); // The new entity added by the newbase // Because we are cloning the entity, we need to restore children temporarely in order to clone them as well entityFromNewBase.Value.PopChildren(); var newEntityDesign = ((AssetCompositeHierarchyData <EntityDesign, Entity>)AssetCloner.Clone(tempHiearchy)).Parts[0]; entityFromNewBase.Value.PushChildren(); var newId = Guid.NewGuid(); newEntityDesign.Entity.Id = newId; newEntityDesign.BaseId = entityId; newEntityDesign.BasePartInstanceId = basePartInstanceId; // Because we are going to modify the NewBase we need to clone it // We tag this entry as special, as we will have to process its children differently later in the merge hierarchy var item = new NewEntityEntry(newEntityDesign, null, newEntities.Count) { IsNewBase = true }; // Add this to the list of entities from newAsset // Specific to the newEntities, GroupPartKey.PartInstanceId is always Guid.Empty newEntities.Add(new GroupPartKey(null, newId), item); // If the entity is coming from a part and is from root Entities, we need to add it to the rootEntities by default if (basePartInstanceId.HasValue && entityFromNewBase.Value.Hierarchy.RootPartIds.Contains(entityId)) { rootEntitiesToAdd.Add(newId); } } } // Compute Entities Removed in newbase (present in base) foreach (var entityFromBase in baseEntities) { var entityId = entityFromBase.Value.EntityDesign.Entity.Id; var key = new GroupPartKey(entityFromBase.Key.PartInstanceId, entityId); if (!newBaseEntities.ContainsKey(key)) { entitiesRemovedInNewBase.Add(entityId); } } // Compute the GUID -> index in list for RootEntities for Base and NewBase var baseRootEntities = new Dictionary <Guid, int>(); if (baseAsset != null) { for (int i = 0; i < baseAsset.Hierarchy.RootPartIds.Count; i++) { var id = baseAsset.Hierarchy.RootPartIds[i]; baseRootEntities.Add(id, i); } } var newBaseRootEntities = new Dictionary <Guid, int>(); if (newBaseAsset != null) { for (int i = 0; i < newBaseAsset.Hierarchy.RootPartIds.Count; i++) { var id = newBaseAsset.Hierarchy.RootPartIds[i]; newBaseRootEntities.Add(id, i); } } // Associate all NewEntity entries to their base foreach (var newEntityEntry in newEntities.ToList()) // use ToList so we can modify the dictionary while iterating { // Skip entities that don't have a base, as we don't have to do anything in the merge var entityDesign = newEntityEntry.Value.EntityDesign; if (!entityDesign.BaseId.HasValue) { continue; } // Else we will associate entries var newEntity = entityDesign.Entity; var baseId = entityDesign.BaseId.Value; var baseKey = new GroupPartKey(entityDesign.BasePartInstanceId, baseId); BaseEntityEntry baseRemap; BaseEntityEntry newBaseRemap; baseEntities.TryGetValue(baseKey, out baseRemap); newBaseEntities.TryGetValue(baseKey, out newBaseRemap); // Link between base and new entity if (baseRemap != null) { baseRemap.NewEntity = newEntityEntry.Value; } if (newBaseRemap != null) { newBaseRemap.NewEntity = newEntityEntry.Value; } // Link the new entity to its base newEntityEntry.Value.Base = baseRemap; newEntityEntry.Value.NewBase = newBaseRemap; if (entitiesRemovedInNewBase.Contains(baseId)) { entitiesToRemoveFromNew.Add(newEntity.Id); // Else the entity has been removed newEntities.Remove(newEntityEntry.Key); } else { // Remap ids in the RootEntities for base int index; if (baseAsset != null && baseRootEntities.TryGetValue(baseId, out index)) { baseRootEntities.Remove(baseId); baseAsset.Hierarchy.RootPartIds[index] = newEntity.Id; } // Remap ids in the RootEntities for newBase if (newBaseAsset != null && newBaseRootEntities.TryGetValue(baseId, out index)) { newBaseRootEntities.Remove(baseId); newBaseAsset.Hierarchy.RootPartIds[index] = newEntity.Id; } } } // Uncomment the following code to dump the content of the different entities and how // they are remapped if (Debug) { System.Diagnostics.Debug.WriteLine(string.Empty); System.Diagnostics.Debug.WriteLine("**********************************"); System.Diagnostics.Debug.WriteLine($"Merge Entity [{debugLocation}] => {newAsset.Id} Base: {baseAsset?.Id}"); System.Diagnostics.Debug.WriteLine("=================================="); Dump("\n**********\nBase\n==========", baseEntities); Dump("\n**********\nNewBase\n==========", newBaseEntities); Dump("\n**********\nNew\n==========", newEntities); } }