Beispiel #1
0
        /// <inheritdoc />
        public override bool ProcessDeserializedData(AssetPropertyGraphContainer graphContainer, object targetRootObject, Type targetMemberType, ref object data, bool isRootDataObjectReference, AssetId?sourceId, YamlAssetMetadata <OverrideType> overrides, YamlAssetPath basePath)
        {
            var asset     = (AssetCompositeHierarchy <TAssetPartDesign, TAssetPart>)targetRootObject;
            var hierarchy = data as AssetCompositeHierarchyData <TAssetPartDesign, TAssetPart>;

            if (hierarchy == null)
            {
                return(false);
            }

            // Create a temporary asset to host the hierarchy to paste, so we have a property graph to manipulate it.
            var tempAsset = (AssetCompositeHierarchy <TAssetPartDesign, TAssetPart>)Activator.CreateInstance(asset.GetType());

            tempAsset.Hierarchy = hierarchy;
            // Use temporary containers so that any created nodes are discarded after the processing.
            var tempNodeContainer = new AssetNodeContainer {
                NodeBuilder = { NodeFactory = new AssetNodeFactory() }
            };
            var definition = AssetQuantumRegistry.GetDefinition(asset.GetType());
            var rootNode   = tempNodeContainer.GetOrCreateNode(tempAsset);

            // If different asset or if at least one part already exists, create a custom clone.
            if (asset.Id != sourceId || hierarchy.Parts.Values.Any(part => asset.ContainsPart(part.Part.Id)))
            {
                // Clone again to create new ids for any IIdentifiable, but keep references to external object intact.
                var cloneExternalReferences = ExternalReferenceCollector.GetExternalReferences(definition, rootNode);
                hierarchy = AssetCloner.Clone(hierarchy, AssetClonerFlags.GenerateNewIdsForIdentifiableObjects, cloneExternalReferences, out var idRemapping);
                // Remap indices of parts in Hierarchy.Part
                AssetCloningHelper.RemapIdentifiablePaths(overrides, idRemapping, basePath);
                // Make new base instances ids in case the part are inherited.
                AssetPartsAnalysis.GenerateNewBaseInstanceIds(hierarchy);
                // Update the temporary asset with this cloned hierarchy.
                rootNode[nameof(AssetCompositeHierarchy <TAssetPartDesign, TAssetPart> .Hierarchy)].Update(hierarchy);
            }

            // Collect all referenceable objects from the target asset (where we're pasting)
            var targetPropertyGraph  = graphContainer.TryGetGraph(asset.Id);
            var referenceableObjects = IdentifiableObjectCollector.Collect(targetPropertyGraph.Definition, targetPropertyGraph.RootNode);

            // Replace references in the hierarchy being pasted by the real objects from the target asset.
            var externalReferences = new HashSet <Guid>(ExternalReferenceCollector.GetExternalReferences(definition, rootNode).Select(x => x.Id));
            var visitor            = new ObjectReferencePathGenerator(definition)
            {
                ShouldOutputReference = x => externalReferences.Contains(x)
            };

            visitor.Visit(rootNode);

            FixupObjectReferences.FixupReferences(tempAsset, visitor.Result, referenceableObjects, true);

            data = hierarchy;

            return(true);
        }
Beispiel #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);
        }
Beispiel #3
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);
        }
Beispiel #4
0
        /// <inheritdoc />
        public override bool ProcessDeserializedData(AssetPropertyGraphContainer graphContainer, object targetRootObject, Type targetMemberType, ref object data, bool isRootDataObjectReference, AssetId?sourceId, YamlAssetMetadata <OverrideType> overrides, YamlAssetPath basePath)
        {
            if (targetRootObject == null)
            {
                throw new ArgumentNullException(nameof(targetRootObject));
            }
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            var asset = (Asset)targetRootObject;
            var targetPropertyGraph = graphContainer.TryGetGraph(asset.Id);

            // We use a container object in case the data itself is an object reference
            var container = isRootDataObjectReference ? new FixupContainer {
                Data = data
            } : data;
            var rootNode           = targetPropertyGraph.Container.NodeContainer.GetOrCreateNode(container);
            var externalReferences = ExternalReferenceCollector.GetExternalReferences(targetPropertyGraph.Definition, rootNode);

            try
            {
                // Clone to create new ids for any IIdentifiable, except passed external references that will be maintained
                Dictionary <Guid, Guid> idRemapping;
                data = AssetCloner.Clone <object>(data, AssetClonerFlags.GenerateNewIdsForIdentifiableObjects, externalReferences, out idRemapping);
            }
            // TODO: have a proper exception type for serialization failure
            catch (Exception)
            {
                // Note: this can fail if the type doesn't have a binary serializer.
                return(false);
            }

            var  targetTypeDescriptor = TypeDescriptorFactory.Default.Find(targetMemberType);
            bool result;

            switch (targetTypeDescriptor.Category)
            {
            case DescriptorCategory.Collection:
                result = ConvertForCollection((CollectionDescriptor)targetTypeDescriptor, ref data);
                break;

            case DescriptorCategory.Dictionary:
                result = ConvertForDictionary((DictionaryDescriptor)targetTypeDescriptor, ref data);
                break;

            case DescriptorCategory.Primitive:
            case DescriptorCategory.Object:
            case DescriptorCategory.NotSupportedObject:
            case DescriptorCategory.Nullable:
                result = ConvertForProperty(targetTypeDescriptor.Type, ref data);
                break;

            case DescriptorCategory.Array:
            case DescriptorCategory.Custom:
                throw new NotSupportedException();

            default:
                throw new ArgumentOutOfRangeException();
            }

            if (!result)
            {
                return(false);
            }

            // Collect all referenceable objects from the target asset (where we're pasting)
            var referenceableObjects = IdentifiableObjectCollector.Collect(targetPropertyGraph.Definition, targetPropertyGraph.RootNode);

            // We use a container object in case the data itself is an object reference
            container = isRootDataObjectReference ? new FixupContainer {
                Data = data
            } : data;
            rootNode = targetPropertyGraph.Container.NodeContainer.GetOrCreateNode(container);

            // Generate YAML paths for the external reference so we can go through the normal deserialization fixup method.
            var externalReferenceIds = new HashSet <Guid>(externalReferences.Select(x => x.Id));
            var visitor = new ObjectReferencePathGenerator(targetPropertyGraph.Definition)
            {
                ShouldOutputReference = x => externalReferenceIds.Contains(x)
            };

            visitor.Visit(rootNode);

            // Fixup external references
            FixupObjectReferences.FixupReferences(container, visitor.Result, referenceableObjects, true);

            data = (container as FixupContainer)?.Data ?? data;
            return(true);
        }