Пример #1
0
        public static string PrintHierarchy(AssetCompositeHierarchy <Types.MyPartDesign, Types.MyPart> asset)
        {
            var stack = new Stack <Tuple <Types.MyPartDesign, int> >();

            asset.Hierarchy.RootParts.Select(x => asset.Hierarchy.Parts[x.Id]).Reverse().ForEach(x => stack.Push(Tuple.Create(x, 0)));
            var sb = new StringBuilder();

            while (stack.Count > 0)
            {
                var current = stack.Pop();
                sb.Append("".PadLeft(current.Item2 * 2));
                sb.AppendLine($"- {current.Item1.Part.Name} [{current.Item1.Part.Id}]");
                foreach (var child in asset.EnumerateChildPartDesigns(current.Item1, asset.Hierarchy, false).Reverse())
                {
                    stack.Push(Tuple.Create(child, current.Item2 + 1));
                }
            }
            var str = sb.ToString();

            return(str);
        }
Пример #2
0
        protected IEnumerable <TAssetPart> ClonePartsForGameSide([NotNull] AssetCompositeHierarchy <TAssetPartDesign, TAssetPart> asset, [NotNull] IEnumerable <TAssetPart> parts)
        {
            var flags           = SubHierarchyCloneFlags.RemoveOverrides;
            var sourceContainer = Asset.PropertyGraph.Container.NodeContainer;
            var targetContainer = GameSideNodeContainer;
            var clone           = AssetCompositeHierarchyPropertyGraph <TAssetPartDesign, TAssetPart> .CloneSubHierarchies(sourceContainer, targetContainer, asset, parts.Select(p => p.Id), flags, out _);

            // Collect external references after cloning, we need to fix them up!
            var rootNode             = GameSideNodeContainer.GetOrCreateNode(clone);
            var definition           = AssetQuantumRegistry.GetDefinition(asset.GetType());
            var unresolvedReferences = ExternalReferenceCollector.GetExternalReferenceAccessors(definition, rootNode);

            // Retrieve all available game-side identifiable objects, so we can try to resolve external references with items from this collection.
            var identifiableObjects = CollectIdentifiableObjects();

            foreach (var reference in unresolvedReferences)
            {
                if (identifiableObjects.TryGetValue(reference.Key.Id, out var realObject))
                {
                    // Target object found, let's update the reference with the real game-side object.
                    foreach (var accessor in reference.Value)
                    {
                        accessor.UpdateValue(realObject);
                    }
                }
                else
                {
                    // Target object not found, let's clear the reference since the currently set object could be asset-side, or a temporary proxy, etc.
                    foreach (var accessor in reference.Value)
                    {
                        accessor.UpdateValue(null);
                    }
                }
            }
            return(clone.RootParts);
        }
Пример #3
0
        /// <summary>
        /// Finds the best index (and parent) at which to insert a new part that is propagated after being added to one of the bases of this asset.
        /// </summary>
        /// <param name="baseAsset">The base asset for the part that has been added.</param>
        /// <param name="newBasePart">The new part that has been added to the base.</param>
        /// <param name="newBasePartParent">The parent part of the part that has been added to the base.</param>
        /// <param name="instanceId">The id of the instance for which we are looking for an index and parent.</param>
        /// <param name="instanceParent">The parent in which to insert the new instance part. If null, the new part will be inserted as root of the hierarchy.</param>
        /// <returns>The index at which to insert the new part in the instance, or a negative value if the part should be discarded.</returns>
        protected virtual int FindBestInsertIndex(AssetCompositeHierarchy <TAssetPartDesign, TAssetPart> baseAsset, TAssetPartDesign newBasePart, TAssetPart newBasePartParent, Guid instanceId, out TAssetPartDesign instanceParent)
        {
            instanceParent = null;
            var insertIndex = -1;

            // First, let's find out where it is the best to insert this new part
            if (newBasePartParent == null)
            {
                // The part is a root, so we must place it according to its sibling (since no parent exists).
                var partIndex = baseAsset.Hierarchy.RootParts.IndexOf(x => x.Id == newBasePart.Part.Id);
                // Let's try to find a sibling in the parts preceding it, in order
                for (var i = partIndex - 1; i >= 0 && insertIndex < 0; --i)
                {
                    var sibling         = baseAsset.Hierarchy.Parts[baseAsset.Hierarchy.RootParts[i].Id];
                    var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Part.Id);
                    // This sibling still exists instance-side, let's get its parent.
                    if (instanceSibling != null)
                    {
                        // If the sibling itself has a parent instance-side, let's use the same parent and insert after it
                        // Otherwise the sibling is root, let's insert after it in the root parts
                        var parent = Asset.GetParent(instanceSibling.Part);
                        instanceParent = parent != null ? Asset.Hierarchy.Parts[parent.Id] : null;
                        insertIndex    = Asset.IndexOf(instanceSibling.Part) + 1;
                        break;
                    }
                }

                // Let's try to find a sibling in the parts following it, in order
                for (var i = partIndex + 1; i < baseAsset.Hierarchy.RootParts.Count && insertIndex < 0; ++i)
                {
                    var sibling         = baseAsset.Hierarchy.Parts[baseAsset.Hierarchy.RootParts[i].Id];
                    var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Part.Id);
                    // This sibling still exists instance-side, let's get its parent.
                    if (instanceSibling != null)
                    {
                        // If the sibling itself has a parent instance-side, let's use the same parent and insert after it
                        // Otherwise the sibling is root, let's insert after it in the root parts
                        var parent = Asset.GetParent(instanceSibling.Part);
                        instanceParent = parent != null ? Asset.Hierarchy.Parts[parent.Id] : null;
                        insertIndex    = Asset.IndexOf(instanceSibling.Part);
                        break;
                    }
                }
            }
            else
            {
                // The new part is not root, it has a parent.
                instanceParent = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == newBasePartParent.Id);

                // If the parent has been removed instance side, the hierarchy to the new part does not exist anymore. We can discard it
                if (instanceParent != null)
                {
                    var partIndex = baseAsset.IndexOf(newBasePart.Part);

                    // Let's try to find a sibling in the parts preceding it, in order
                    for (var i = partIndex - 1; i >= 0 && insertIndex < 0; --i)
                    {
                        var sibling         = baseAsset.GetChild(newBasePartParent, i);
                        var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Id);
                        // This sibling still exists instance-side, let's insert after it
                        if (instanceSibling != null)
                        {
                            insertIndex = i + 1;
                        }
                    }

                    // Let's try to find a sibling in the parts following it, in order
                    for (var i = partIndex + 1; i < baseAsset.GetChildCount(newBasePartParent) && insertIndex < 0; ++i)
                    {
                        var sibling         = baseAsset.GetChild(newBasePartParent, i);
                        var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Id);
                        // This sibling still exists instance-side, let's insert before it
                        if (instanceSibling != null)
                        {
                            insertIndex = i - 1;
                        }
                    }

                    // Default position is first index
                    if (insertIndex < 0)
                    {
                        insertIndex = 0;
                    }
                }
            }

            if (insertIndex < 0)
            {
                // We couldn't find any parent/sibling in the instance. Either the parent has been removed, in which case we'll discard the part,
                // or the base is a single part that has been moved around, and we'll rely on the last known common ancestor of this instance to re-insert it.
                var isAlone = Asset.Hierarchy.Parts.Values.All(x => x.Base?.InstanceId != instanceId);
                if (isAlone)
                {
                    Guid parentId;
                    if (instancesCommonAncestors.TryGetValue(instanceId, out parentId) && parentId != Guid.Empty)
                    {
                        // FIXME: instancesCommonAncestors should be synchronized with existing instances, i.e. if the instance has been compltely removed then the common ancestor should have been cleared.
                        if (Asset.Hierarchy.Parts.TryGetValue(parentId, out instanceParent))
                        {
                            insertIndex = 0;
                        }
                    }
                }
            }
            return(insertIndex);
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
 /// <summary>
 /// Clones a sub-hierarchy of a composite hierarchical asset.
 /// </summary>
 /// <param name="nodeContainer">The container in which are the nodes of the hierarchy to clone and in which to create nodes for the cloned hierarchy, used to propagate 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 nodeContainer, [NotNull] AssetCompositeHierarchy <TAssetPartDesign, TAssetPart> asset,
                                                                                              [NotNull] IEnumerable <Guid> sourceRootIds, SubHierarchyCloneFlags flags, [NotNull] out Dictionary <Guid, Guid> idRemapping)
 {
     return(CloneSubHierarchies(nodeContainer, nodeContainer, asset, sourceRootIds, flags, out idRemapping));
 }
Пример #6
0
 protected TAssetPart ClonePartForGameSide([NotNull] AssetCompositeHierarchy <TAssetPartDesign, TAssetPart> asset, [NotNull] TAssetPart part)
 {
     return(ClonePartsForGameSide(asset, part.Yield()).Single());
 }