Beispiel #1
0
        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>
        /// This method will remove all entities that are finally not in the hierarchy (potentially removed by previous pass)
        /// </summary>
        private void FixHierarchy()
        {
            var entitiesNotInHierarchy = newAsset.Hierarchy.Entities.ToDictionary(entityDesign => entityDesign.Entity.Id);

            foreach (var entityId in entitiesInHierarchy)
            {
                entitiesNotInHierarchy.Remove(entityId);
            }

            // Add to the existing list of entities removed and remove from current entities
            foreach (var entityEntry in entitiesNotInHierarchy)
            {
                var entityId = entityEntry.Key;
                entitiesToRemoveFromNew.Add(entityId);

                newAsset.Hierarchy.Entities.Remove(entityId);
            }

            // Collect PartInstanceId,baseId => newId
            // If no group part (plain base), use the empty Guid
            var finalMapBaseIdToNewId = new Dictionary <GroupPartKey, Guid>();

            foreach (var entityEntry in newAsset.Hierarchy.Entities)
            {
                if (entityEntry.Design.BaseId.HasValue)
                {
                    var baseId   = entityEntry.Design.BaseId.Value;
                    var groupKey = new GroupPartKey(entityEntry.Design.BasePartInstanceId, baseId);
                    finalMapBaseIdToNewId[groupKey] = entityEntry.Entity.Id;
                }
            }

            // If there were any references to entities that have been removed, we need to clean them
            foreach (var entityEntry in newAsset.Hierarchy.Entities)
            {
                FixReferencesToEntities(entityEntry, finalMapBaseIdToNewId);
            }
        }
        private void MapEntities(EntityHierarchyData hierarchyData, Dictionary <GroupPartKey, EntityRemapEntry> entities, Guid?instancePartIdArg = null)
        {
            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 = (EntityHierarchyData)AssetCloner.Clone(hierarchyData);
            }


            foreach (var entityDesign in hierarchyData.Entities)
            {
                if (instancePartIdArg.HasValue)
                {
                    entityDesign.Design.BasePartInstanceId = instancePartIdArg;
                }

                var key = new GroupPartKey(instancePartIdArg, entityDesign.Entity.Id);

                if (entities.ContainsKey(key))
                {
                    continue;
                }

                var remap = new EntityRemapEntry(entityDesign, hierarchyData);
                // We are removing children from transform component for the diff
                remap.PushChildren();

                entities[key] = remap;
            }
        }
        /// <summary>
        /// Prepare the merge by computing internal dictionaries
        /// </summary>
        private void PrepareMerge()
        {
            // Prepare mapping for new asset
            MapEntities(newAsset.Hierarchy, newEntities);

            // Prepare mappings for base
            MapEntities(baseAsset?.Hierarchy, baseEntities);
            if (newAsset.BaseParts != null)
            {
                foreach (var partItem in newAsset.GetBasePartInstanceIds())
                {
                    foreach (var groupPartId in partItem.Value)
                    {
                        MapEntities(partItem.Key.Hierarchy, baseEntities, groupPartId);
                    }
                }
            }


            // Prepare mapping for new base
            MapEntities(newBaseAsset?.Hierarchy, newBaseEntities);
            if (newBaseParts != null)
            {
                foreach (var partItem in newAsset.GetBasePartInstanceIds(newBaseParts))
                {
                    foreach (var groupPartId in partItem.Value)
                    {
                        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(entityFromNewBase.Key.PartInstanceId, entityId);

                if (!baseEntities.ContainsKey(key))
                {
                    var item = entityFromNewBase.Value;

                    // The new entity added by the newbase
                    var baseId = item.EntityDesign.Entity.Id;
                    var newId  = Guid.NewGuid();
                    item.EntityDesign.Entity.Id                 = newId;
                    item.EntityDesign.Design.BaseId             = baseId;
                    item.EntityDesign.Design.BasePartInstanceId = basePartInstanceId;

                    // 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.RootEntities.Contains(baseId))
                    {
                        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);
                }
            }

            var baseRootEntities = new Dictionary <Guid, int>();

            if (baseAsset != null)
            {
                for (int i = 0; i < baseAsset.Hierarchy.RootEntities.Count; i++)
                {
                    var id = baseAsset.Hierarchy.RootEntities[i];
                    baseRootEntities.Add(id, i);
                }
            }
            var newBaseRootEntities = new Dictionary <Guid, int>();

            if (newBaseAsset != null)
            {
                for (int i = 0; i < newBaseAsset.Hierarchy.RootEntities.Count; i++)
                {
                    var id = newBaseAsset.Hierarchy.RootEntities[i];
                    newBaseRootEntities.Add(id, i);
                }
            }

            // Prebuild the list of entities that we will have to remove
            foreach (var entityEntry in newEntities.ToList()) // use ToList so we can modify the dictionary while iterating
            {
                var entityDesign = entityEntry.Value.EntityDesign;
                var newEntity    = entityDesign.Entity;

                if (entityDesign.Design.BaseId.HasValue)
                {
                    var baseId = entityDesign.Design.BaseId.Value;

                    if (entitiesRemovedInNewBase.Contains(baseId))
                    {
                        entitiesToRemoveFromNew.Add(newEntity.Id);

                        // Else the entity has been removed
                        newEntities.Remove(entityEntry.Key);
                    }
                    else
                    {
                        var baseKey = new GroupPartKey(entityDesign.Design.BasePartInstanceId, baseId);

                        EntityRemapEntry baseRemap;
                        EntityRemapEntry newBaseRemap;
                        baseEntities.TryGetValue(baseKey, out baseRemap);
                        newBaseEntities.TryGetValue(baseKey, out newBaseRemap);
                        entityEntry.Value.Base    = baseRemap;
                        entityEntry.Value.NewBase = newBaseRemap;

                        // Remap ids in the RootEntities for base
                        int index;
                        if (baseAsset != null && baseRootEntities.TryGetValue(baseId, out index))
                        {
                            baseRootEntities.Remove(baseId);
                            baseAsset.Hierarchy.RootEntities[index] = newEntity.Id;
                        }

                        // Remap ids in the RootEntities for newBase
                        if (newBaseAsset != null && newBaseRootEntities.TryGetValue(baseId, out index))
                        {
                            newBaseRootEntities.Remove(baseId);
                            newBaseAsset.Hierarchy.RootEntities[index] = newEntity.Id;
                        }
                    }
                }
            }
        }
        private void FixReferencesToEntities(EntityDesign newEntityDesign, Dictionary <GroupPartKey, Guid> mapBaseIdToNewId)
        {
            var newEntity = newEntityDesign.Entity;

            // We need to visit all references to entities/components in order to fix references
            // (e.g entities removed, entity added from base referencing an entity from base that we have to redirect to the new child entity...)
            // Suppose for example that:
            //
            // base   newBase                                      newAsset
            // EA       EA                                           EA'
            // EB       EB                                           EB'
            // EC       EC                                           EC'
            //          ED (+link to EA via script or whather)       ED' + link to EA' (we need to change from EA to EA')
            //
            // So in the example above, when merging ED into newAsset, all references to entities declared in newBase are not automatically
            // remapped to the new entities in newAsset. This is the purpose of this whole method

            var entityVisitor          = new SingleLevelVisitor(typeof(Entity), true);
            var entityComponentVisitor = new SingleLevelVisitor(typeof(EntityComponent), true);

            DataVisitNodeBuilder.Run(TypeDescriptorFactory.Default, newEntity, new List <IDataCustomVisitor>()
            {
                entityVisitor,
                entityComponentVisitor
            });

            // Fix Entity and EntityComponent references
            foreach (var idNodes in entityVisitor.References.Concat(entityComponentVisitor.References))
            {
                var id    = idNodes.Key;
                var nodes = idNodes.Value;

                // If entity id is not in the current list, it is more likely that it was a link to a base entity
                if (!newAsset.Hierarchy.Entities.ContainsKey(id))
                {
                    var groupKey = new GroupPartKey(newEntityDesign.Design.BasePartInstanceId, id);

                    // We are trying to remap the base id to the new id from known entities from newAsset
                    Guid newId;
                    if (mapBaseIdToNewId.TryGetValue(groupKey, out newId))
                    {
                        var linkedEntity = newAsset.Hierarchy.Entities[newId].Entity;
                        foreach (var node in nodes)
                        {
                            var entityComponent = node.Instance as EntityComponent;
                            if (entityComponent != null)
                            {
                                // TODO: In case of a DataVisitMember node, we need to set an OverrideType to New if we are actually removing a base value
                                var newEntityComponent = (EntityComponent)linkedEntity.Components.Get(entityComponent.GetDefaultKey());
                                node.SetValue(newEntityComponent);
                            }
                            else
                            {
                                // TODO: In case of a DataVisitMember node, we need to set an OverrideType to New if we are actually removing a base value
                                // Else the node applies to an entity
                                node.SetValue(linkedEntity);
                            }
                        }
                    }
                    else
                    {
                        // TODO: In case of a DataVisitMember node, we need to set an OverrideType to New if we are actually removing a base value
                        // If we are trying to link to an entity/component that was removed, we need to remove it
                        foreach (var node in nodes)
                        {
                            node.RemoveValue();
                        }
                    }
                }
            }
        }
Beispiel #6
0
        /// <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);
            }
        }