public AssetCompositeHierarchyData <UIElementDesign, UIElement> CreateElementInstance(UIAssetBase targetContainer, [NotNull] string targetLocation, Guid elementId, out Guid instanceId) { // TODO: make a common base method in AssetCompositeHierarchy - the beginning of the method is similar to CreatePrefabInstance var idRemapping = new Dictionary <Guid, Guid>(); var instance = (UILibraryAsset)CreateDerivedAsset(targetLocation, out idRemapping); var rootElementId = idRemapping[elementId]; if (instance.Hierarchy.RootParts.All(x => x.Id != rootElementId)) { throw new ArgumentException(@"The given id cannot be found in the root parts of this library.", nameof(elementId)); } instanceId = instance.Hierarchy.Parts.Values.FirstOrDefault()?.Base?.InstanceId ?? Guid.NewGuid(); var result = new AssetCompositeHierarchyData <UIElementDesign, UIElement>(); result.RootParts.Add(instance.Hierarchy.Parts[rootElementId].UIElement); result.Parts.Add(instance.Hierarchy.Parts[rootElementId]); foreach (var element in this.EnumerateChildPartDesigns(instance.Hierarchy.Parts[rootElementId], instance.Hierarchy, true)) { result.Parts.Add(element); } return(result); }
/// <summary> /// Remaps the parts identifier. /// </summary> /// <typeparam name="TAssetPartDesign"></typeparam> /// <typeparam name="TAssetPart">The underlying type of part.</typeparam> /// <param name="hierarchy">The hierarchy of parts.</param> /// <param name="idRemapping">The identifier remapping.</param> public static void RemapPartsId <TAssetPartDesign, TAssetPart>(AssetCompositeHierarchyData <TAssetPartDesign, TAssetPart> hierarchy, IDictionary <Guid, Guid> idRemapping) where TAssetPartDesign : class, IAssetPartDesign <TAssetPart> where TAssetPart : class, IIdentifiable { Guid newId; // Remap parts in asset2 with new Id for (var i = 0; i < hierarchy.RootPartIds.Count; ++i) { if (idRemapping.TryGetValue(hierarchy.RootPartIds[i], out newId)) { hierarchy.RootPartIds[i] = newId; } } foreach (var part in hierarchy.Parts) { if (idRemapping.TryGetValue(part.Part.Id, out newId)) { part.Part.Id = newId; } } // Sort again the hierarchy (since the Ids changed) hierarchy.Parts.Sort(); }
/// <summary> /// Creates a instance of the given control that can be added to another <see cref="UIAssetBase"/>. /// </summary> /// <param name="targetContainer">The container in which the instance will be added.</param> /// <param name="targetLocation">The location of this asset.</param> /// <param name="elementId">The id of the element to instantiate.</param> /// <param name="instanceId">The identifier of the created instance.</param> /// <returns>An <see cref="AssetCompositeHierarchyData{UIElementDesign, UIElement}"/> containing the cloned elements of </returns> /// <remarks>This method will update the <see cref="Asset.BaseParts"/> property of the <see paramref="targetContainer"/>.</remarks> public AssetCompositeHierarchyData <UIElementDesign, UIElement> CreateElementInstance(UIAssetBase targetContainer, string targetLocation, Guid elementId, out Guid instanceId) { // TODO: make a common base method in AssetCompositeHierarchy - the beginning of the method is similar to CreatePrefabInstance var idRemapping = new Dictionary <Guid, Guid>(); var instance = (UILibraryAsset)CreateChildAsset(targetLocation, idRemapping); var rootElementId = idRemapping[elementId]; if (!instance.Hierarchy.RootPartIds.Contains(rootElementId)) { throw new ArgumentException(@"The given id cannot be found in the root parts of this library.", nameof(elementId)); } targetContainer.AddBasePart(instance.Base); instanceId = Guid.NewGuid(); foreach (var elementEntry in instance.Hierarchy.Parts) { elementEntry.BasePartInstanceId = instanceId; } var result = new AssetCompositeHierarchyData <UIElementDesign, UIElement>(); result.RootPartIds.Add(rootElementId); result.Parts.Add(instance.Hierarchy.Parts[rootElementId]); foreach (var element in EnumerateChildParts(instance.Hierarchy.Parts[rootElementId], instance.Hierarchy, true)) { result.Parts.Add(element); } return(result); }
/// <summary> /// Clones a sub-hierarchy of this asset. /// </summary> /// <param name="sourceRootId">The id of the root of the sub-hierarchy to clone</param> /// <param name="cleanReference">If true, any reference to a part external to the cloned hierarchy will be set to null.</param> /// <param name="idRemapping">A dictionary containing the mapping of ids from the source parts to the new parts.</param> /// <returns>A <see cref="AssetCompositeHierarchyData{UIElementDesign, UIElement}"/> corresponding to the cloned parts.</returns> public override AssetCompositeHierarchyData <UIElementDesign, UIElement> CloneSubHierarchy(Guid sourceRootId, bool cleanReference, out Dictionary <Guid, Guid> idRemapping) { if (!Hierarchy.Parts.ContainsKey(sourceRootId)) { throw new ArgumentException(@"The source root part must be an part of this asset.", nameof(sourceRootId)); } // Note: Instead of copying the whole asset (with its potentially big hierarchy), // we first copy the asset only (without the hierarchy), then the sub-hierarchy to extract. var subTreeRoot = Hierarchy.Parts[sourceRootId]; var subTreeHierarchy = new AssetCompositeHierarchyData <UIElementDesign, UIElement> { Parts = { subTreeRoot }, RootPartIds = { sourceRootId } }; foreach (var subTreeDesign in EnumerateChildParts(subTreeRoot, Hierarchy, true)) { subTreeHierarchy.Parts.Add(Hierarchy.Parts[subTreeDesign.UIElement.Id]); } // clone the parts of the sub-tree var clonedHierarchy = (AssetCompositeHierarchyData <UIElementDesign, UIElement>)AssetCloner.Clone(subTreeHierarchy); //clonedHierarchy.Parts[sourceRootEntity].UIElement.Parent = null; //if (cleanReference) //{ // // set to null reference outside of the sub-tree // var tempAsset = new PrefabAsset { Hierarchy = clonedHierarchy }; // tempAsset.FixupPartReferences(); //} // temporary nullify the hierarchy to avoid to clone it var sourceHierarchy = Hierarchy; Hierarchy = null; // revert the source hierarchy Hierarchy = sourceHierarchy; // Generate part mapping idRemapping = new Dictionary <Guid, Guid>(); foreach (var partDesign in clonedHierarchy.Parts) { // Generate new Id var newPartId = Guid.NewGuid(); // Update mappings idRemapping.Add(partDesign.UIElement.Id, newPartId); // Update part with new id partDesign.UIElement.Id = newPartId; } // Rewrite part references // Should we nullify invalid references? AssetPartsAnalysis.RemapPartsId(clonedHierarchy, idRemapping); return(clonedHierarchy); }
/// <inheritdoc/> protected override void ClearPartReferences(AssetCompositeHierarchyData <UIElementDesign, UIElement> clonedHierarchy) { // set to null reference outside of the sub-tree var tempAsset = new UILibraryAsset { Hierarchy = clonedHierarchy }; tempAsset.FixupPartReferences(); }
/// <inheritdoc/> protected override void ClearPartReferences(AssetCompositeHierarchyData <EntityDesign, Entity> clonedHierarchy) { // set to null reference outside of the sub-tree var tempAsset = new PrefabAsset { Hierarchy = clonedHierarchy }; tempAsset.FixupPartReferences(); }
public EntityEntry(EntityDesign entityDesign, AssetCompositeHierarchyData <EntityDesign, Entity> hierarchy, int order) { if (entityDesign == null) { throw new ArgumentNullException(nameof(entityDesign)); } Order = order; Hierarchy = hierarchy; EntityDesign = entityDesign; // Remove children from transform component in order to process them later PushChildren(); }
/// <summary> /// Helper method to dump this hierarchy to a text output /// </summary> /// <param name="hierarchy"></param> /// <param name="writer"></param> /// <returns><c>true</c> if the dump was sucessful, <c>false</c> otherwise</returns> public static bool DumpTo(this AssetCompositeHierarchyData <EntityDesign, Entity> hierarchy, TextWriter writer) { bool result = true; writer.WriteLine("***************"); writer.WriteLine($"RootEntities [{hierarchy.RootPartIds.Count}]"); writer.WriteLine("==============="); foreach (var id in hierarchy.RootPartIds) { if (!hierarchy.Parts.ContainsKey(id)) { result = false; } writer.WriteLine(hierarchy.Parts.ContainsKey(id) ? $"{id} => {hierarchy.Parts[id].Entity}" : $"{id} => ERROR - Entity not found in [Entities]"); } writer.WriteLine("***************"); writer.WriteLine($"Entities [{hierarchy.Parts.Count}]"); writer.WriteLine("==============="); for (int i = 0; i < hierarchy.Parts.Count; i++) { var entityEntry = hierarchy.Parts[i]; writer.Write($"{entityEntry.Entity.Id} => {entityEntry.Entity}"); if (entityEntry.BaseId != null) { writer.Write($" Base: {entityEntry.BaseId}"); } if (entityEntry.BasePartInstanceId != null) { writer.Write($" BasePartInstanceId: {entityEntry.BasePartInstanceId}"); } writer.WriteLine(); foreach (var child in entityEntry.Entity.Transform.Children) { writer.Write($" - {child.Entity.Id} => {child.Entity.Name}"); if (!hierarchy.Parts.ContainsKey(child.Entity.Id)) { writer.Write(" <= ERROR, Entity not found in [Entities]"); result = false; } writer.WriteLine(); } } return(result); }
/// <summary> /// When a part is added to the base asset, it could be the result of a move (remove + add). /// In that case, remove the new <paramref name="clonedPart"/> and replace it with the <paramref name="existingPart"/>. /// </summary> /// <param name="baseHierarchy">The cloned base hierarchy.</param> /// <param name="clonedPart">The cloned part to replace.</param> /// <param name="existingPart">The existing part to restore.</param> /// <seealso cref="PartAddedInBaseAsset"/> /// <remarks> /// Inheriting instance can override this method to perform additional operations. /// </remarks> protected virtual void ReuseExistingPart([NotNull] AssetCompositeHierarchyData <TAssetPartDesign, TAssetPart> baseHierarchy, [NotNull] TAssetPartDesign clonedPart, [NotNull] TAssetPartDesign existingPart) { // Replace the cloned part by the one to restore in the list of root if needed if (baseHierarchy.RootParts.Remove(clonedPart.Part)) { baseHierarchy.RootParts.Add(existingPart.Part); } // Replace the cloned part by the one to restore in the list of parts if (!baseHierarchy.Parts.Remove(clonedPart.Part.Id)) { throw new InvalidOperationException("The new part should be in the baseHierarchy."); } baseHierarchy.Parts.Add(existingPart); }
/// <summary> /// Assigns new unique identifiers for base part <see cref="BasePart.InstanceId"/> in the given <paramref name="hierarchy"/>. /// </summary> /// <typeparam name="TAssetPartDesign"></typeparam> /// <typeparam name="TAssetPart">The underlying type of part.</typeparam> /// <param name="hierarchy">The hierarchy which part groups should have new identifiers.</param> public static void GenerateNewBaseInstanceIds <TAssetPartDesign, TAssetPart>(AssetCompositeHierarchyData <TAssetPartDesign, TAssetPart> hierarchy) where TAssetPartDesign : class, IAssetPartDesign <TAssetPart> where TAssetPart : class, IIdentifiable { var baseInstanceMapping = new Dictionary <Guid, Guid>(); foreach (var part in hierarchy.Parts.Where(x => x.Base != null)) { Guid newInstanceId; if (!baseInstanceMapping.TryGetValue(part.Base.InstanceId, out newInstanceId)) { newInstanceId = Guid.NewGuid(); baseInstanceMapping.Add(part.Base.InstanceId, newInstanceId); } part.Base = new BasePart(part.Base.BasePartAsset, part.Base.BasePartId, newInstanceId); } }
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> /// Creates a instance of the given control that can be added to another <see cref="UIAssetBase"/>. /// </summary> /// <param name="targetContainer">The container in which the instance will be added.</param> /// <param name="targetLocation">The location of this asset.</param> /// <param name="elementId">The id of the element to instantiate.</param> /// <param name="instanceId">The identifier of the created instance.</param> /// <returns>An <see cref="AssetCompositeHierarchyData{UIElementDesign, UIElement}"/> containing the cloned elements of </returns> /// <remarks>This method will update the <see cref="Asset.BaseParts"/> property of the <see paramref="targetContainer"/>.</remarks> public AssetCompositeHierarchyData<UIElementDesign, UIElement> CreateElementInstance(UIAssetBase targetContainer, string targetLocation, Guid elementId, out Guid instanceId) { // TODO: make a common base method in AssetCompositeHierarchy - the beginning of the method is similar to CreatePrefabInstance var idRemapping = new Dictionary<Guid, Guid>(); var instance = (UILibraryAsset)CreateDerivedAsset(targetLocation, idRemapping); var rootElementId = idRemapping[elementId]; if (!instance.Hierarchy.RootPartIds.Contains(rootElementId)) throw new ArgumentException(@"The given id cannot be found in the root parts of this library.", nameof(elementId)); instanceId = instance.Hierarchy.Parts.FirstOrDefault()?.Base.InstanceId ?? Guid.NewGuid(); var result = new AssetCompositeHierarchyData<UIElementDesign, UIElement>(); result.RootPartIds.Add(rootElementId); result.Parts.Add(instance.Hierarchy.Parts[rootElementId]); foreach (var element in EnumerateChildParts(instance.Hierarchy.Parts[rootElementId], instance.Hierarchy, true)) { result.Parts.Add(element); } return result; }
/// <summary> /// Remaps the entities identifier. /// </summary> /// <param name="entityHierarchy">The entity hierarchy.</param> /// <param name="idRemapping">The identifier remapping.</param> public static void RemapEntitiesId(AssetCompositeHierarchyData <EntityDesign, Entity> entityHierarchy, Dictionary <Guid, Guid> idRemapping) { Guid newId; // Remap entities in asset2 with new Id for (int i = 0; i < entityHierarchy.RootPartIds.Count; ++i) { if (idRemapping.TryGetValue(entityHierarchy.RootPartIds[i], out newId)) { entityHierarchy.RootPartIds[i] = newId; } } foreach (var entity in entityHierarchy.Parts) { if (idRemapping.TryGetValue(entity.Entity.Id, out newId)) { entity.Entity.Id = newId; } } // Sort again the EntityCollection (since ID changed) entityHierarchy.Parts.Sort(); }
public NewEntityEntry(EntityDesign entityDesign, AssetCompositeHierarchyData <EntityDesign, Entity> hierarchy, int order) : base(entityDesign, hierarchy, order) { }
/// <summary> /// Clones a sub-hierarchy of this asset. /// </summary> /// <param name="sourceRootEntity">The entity that is the root of the sub-hierarchy to clone</param> /// <param name="cleanReference">If true, any reference to an entity external to the cloned hierarchy will be set to null.</param> /// <param name="entityMapping">A dictionary containing the mapping of ids from the source entites to the new entities.</param> /// <returns>A <see cref="AssetCompositeHierarchyData{EntityDesign, Entity}"/> corresponding to the cloned entities.</returns> public AssetCompositeHierarchyData <EntityDesign, Entity> CloneSubHierarchy(Guid sourceRootEntity, bool cleanReference, out Dictionary <Guid, Guid> entityMapping) { if (!Hierarchy.Parts.ContainsKey(sourceRootEntity)) { throw new ArgumentException(@"The source root entity must be an entity of this asset.", nameof(sourceRootEntity)); } // Note: Instead of copying the whole asset (with its potentially big hierarchy), // we first copy the asset only (without the hierarchy), then the sub-hierarchy to extract. var subTreeRoot = Hierarchy.Parts[sourceRootEntity]; var subTreeHierarchy = new AssetCompositeHierarchyData <EntityDesign, Entity> { Parts = { subTreeRoot }, RootPartIds = { sourceRootEntity } }; foreach (var subTreeEntity in EnumerateChildParts(subTreeRoot, true)) { subTreeHierarchy.Parts.Add(Hierarchy.Parts[subTreeEntity.Entity.Id]); } // clone the entities of the sub-tree var clonedHierarchy = (AssetCompositeHierarchyData <EntityDesign, Entity>)AssetCloner.Clone(subTreeHierarchy); clonedHierarchy.Parts[sourceRootEntity].Entity.Transform.Parent = null; if (cleanReference) { // set to null reference outside of the sub-tree var tempAsset = new PrefabAsset { Hierarchy = clonedHierarchy }; tempAsset.FixupPartReferences(); } // temporary nullify the hierarchy to avoid to clone it var sourceHierarchy = Hierarchy; Hierarchy = null; // revert the source hierarchy Hierarchy = sourceHierarchy; // Generate entity mapping entityMapping = new Dictionary <Guid, Guid>(); foreach (var entityDesign in clonedHierarchy.Parts) { // Generate new Id var newEntityId = Guid.NewGuid(); // Update mappings entityMapping.Add(entityDesign.Entity.Id, newEntityId); // Update entity with new id entityDesign.Entity.Id = newEntityId; } // Rewrite entity references // Should we nullify invalid references? EntityAnalysis.RemapEntitiesId(clonedHierarchy, entityMapping); return(clonedHierarchy); }
/// <summary> /// Clones a sub-hierarchy of a composite hierarchical asset. /// </summary> /// <param name="sourceNodeContainer">The container in which are the nodes of the hierarchy to clone, used to extract metadata (overrides, etc.) if needed.</param> /// <param name="targetNodeContainer">The container in which the nodes of the cloned hierarchy should be created, used to re-apply metadata (overrides, etc.) if needed.</param> /// <param name="asset">The asset from which to clone sub-hierarchies.</param> /// <param name="sourceRootIds">The ids that are the roots of the sub-hierarchies to clone.</param> /// <param name="flags">The flags customizing the cloning operation.</param> /// <param name="idRemapping">A dictionary containing the remapping of <see cref="IIdentifiable.Id"/> if <see cref="AssetClonerFlags.GenerateNewIdsForIdentifiableObjects"/> has been passed to the cloner.</param> /// <returns>A <see cref="AssetCompositeHierarchyData{TAssetPartDesign, TAssetPart}"/> corresponding to the cloned parts.</returns> /// <remarks>The parts passed to this methods must be independent in the hierarchy.</remarks> public static AssetCompositeHierarchyData <TAssetPartDesign, TAssetPart> CloneSubHierarchies([NotNull] AssetNodeContainer sourceNodeContainer, [NotNull] AssetNodeContainer targetNodeContainer, [NotNull] AssetCompositeHierarchy <TAssetPartDesign, TAssetPart> asset, [NotNull] IEnumerable <Guid> sourceRootIds, SubHierarchyCloneFlags flags, [NotNull] out Dictionary <Guid, Guid> idRemapping) { if (sourceNodeContainer == null) { throw new ArgumentNullException(nameof(sourceNodeContainer)); } if (targetNodeContainer == null) { throw new ArgumentNullException(nameof(targetNodeContainer)); } if (asset == null) { throw new ArgumentNullException(nameof(asset)); } if (sourceRootIds == null) { throw new ArgumentNullException(nameof(sourceRootIds)); } // Extract the actual sub hierarchies to clone from the asset into a new instance of AssetCompositeHierarchyData var subTreeHierarchy = new AssetCompositeHierarchyData <TAssetPartDesign, TAssetPart>(); foreach (var rootId in sourceRootIds) { if (!asset.Hierarchy.Parts.ContainsKey(rootId)) { throw new ArgumentException(@"The source root parts must be parts of this asset.", nameof(sourceRootIds)); } subTreeHierarchy.RootParts.Add(asset.Hierarchy.Parts[rootId].Part); subTreeHierarchy.Parts.Add(asset.Hierarchy.Parts[rootId]); foreach (var subTreePart in asset.EnumerateChildParts(asset.Hierarchy.Parts[rootId].Part, true)) { subTreeHierarchy.Parts.Add(asset.Hierarchy.Parts[subTreePart.Id]); } } var assetType = asset.GetType(); // Create a new empty asset of the same type, and assign the sub hierachies to clone to it var cloneAsset = (AssetCompositeHierarchy <TAssetPartDesign, TAssetPart>)Activator.CreateInstance(assetType); cloneAsset.Hierarchy = subTreeHierarchy; var assetDefinition = AssetQuantumRegistry.GetDefinition(assetType); // We get the node corresponding to the new asset in the source NodeContainer, to be able to generate metadata (overrides, object references) needed for cloning. var rootNode = sourceNodeContainer.GetOrCreateNode(cloneAsset); var externalReferences = ExternalReferenceCollector.GetExternalReferences(assetDefinition, rootNode); var overrides = (flags & SubHierarchyCloneFlags.RemoveOverrides) == 0 ? GenerateOverridesForSerialization(rootNode) : null; // Now we ready to clone, let's just translate the flags and pass everything to the asset cloner. var clonerFlags = AssetClonerFlags.None; if ((flags & SubHierarchyCloneFlags.GenerateNewIdsForIdentifiableObjects) != 0) { clonerFlags |= AssetClonerFlags.GenerateNewIdsForIdentifiableObjects; } if ((flags & SubHierarchyCloneFlags.CleanExternalReferences) != 0) { clonerFlags |= AssetClonerFlags.ClearExternalReferences; } // We don't need to clone the asset itself, just the hierarchy. The asset itself is just useful so the property graph is in a normal context to do what we need. var clonedHierarchy = AssetCloner.Clone(subTreeHierarchy, clonerFlags, externalReferences, out idRemapping); if ((flags & SubHierarchyCloneFlags.RemoveOverrides) == 0) { // We need to propagate the override information to the nodes of the cloned objects into the target node container. // Let's reuse our temporary asset, and get its node in the target node container. rootNode = targetNodeContainer.GetOrCreateNode(cloneAsset); // Replace the initial hierarchy by the cloned one (through the Update method, in case the target container is the same as the source one). rootNode[nameof(AssetCompositeHierarchy <TAssetPartDesign, TAssetPart> .Hierarchy)].Update(clonedHierarchy); // Remap the paths to overriden properties in case we generated new ids for identifiable objects. AssetCloningHelper.RemapIdentifiablePaths(overrides, idRemapping); // Finally apply the overrides that come from the source parts. ApplyOverrides((IAssetNode)rootNode, overrides); } return(clonedHierarchy); }
/// <inheritdoc /> protected override void ReuseExistingPart(AssetCompositeHierarchyData <EntityDesign, Entity> baseHierarchy, EntityDesign clonedPart, EntityDesign existingPart) { // Update the folder information existingPart.Folder = clonedPart.Folder; base.ReuseExistingPart(baseHierarchy, clonedPart, existingPart); }
public abstract void ReplaceRootElement([NotNull] PanelViewModel sourcePanel, [NotNull] AssetCompositeHierarchyData <UIElementDesign, UIElement> hierarchy, Guid targetPanelId);
/// <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); } }