/// <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); }
/// <inheritdoc/> protected override Dictionary <Guid, IIdentifiable> CollectIdentifiableObjects() { var allElements = RootElements.Values.BreadthFirst(x => x.VisualChildren); var definition = AssetQuantumRegistry.GetDefinition(Asset.Asset.GetType()); var identifiableObjects = new Dictionary <Guid, IIdentifiable>(); foreach (var entityNode in allElements.Select(x => GameSideNodeContainer.GetOrCreateNode(x))) { foreach (var identifiable in IdentifiableObjectCollector.Collect(definition, entityNode)) { identifiableObjects.Add(identifiable.Key, identifiable.Value); } } return(identifiableObjects); }
/// <inheritdoc/> protected override Dictionary <Guid, IIdentifiable> CollectIdentifiableObjects() { var allEntities = Game.ContentScene.Yield().BreadthFirst(x => x.Children).SelectMany(x => x.Entities).BreadthFirst(x => x.Transform.Children.Select(y => y.Entity)); var definition = AssetQuantumRegistry.GetDefinition(Asset.Asset.GetType()); var identifiableObjects = new Dictionary <Guid, IIdentifiable>(); foreach (var entityNode in allEntities.Select(x => GameSideNodeContainer.GetOrCreateNode(x))) { foreach (var identifiable in IdentifiableObjectCollector.Collect(definition, entityNode)) { identifiableObjects.Add(identifiable.Key, identifiable.Value); } } return(identifiableObjects); }
/// <summary> /// Deletes the given parts and all its children, recursively, and clear all object references to it. /// </summary> /// <param name="partDesigns">The parts to delete.</param> /// <param name="deletedPartsMapping">A mapping of the base information (base part id, instance id) of the deleted parts that have a base.</param> public void DeleteParts([NotNull] IEnumerable <TAssetPartDesign> partDesigns, [NotNull] out HashSet <Tuple <Guid, Guid> > deletedPartsMapping) { if (partDesigns == null) { throw new ArgumentNullException(nameof(partDesigns)); } var partsToDelete = new Stack <TAssetPartDesign>(partDesigns); var referencesToClear = new HashSet <Guid>(); deletedPartsMapping = new HashSet <Tuple <Guid, Guid> >(); while (partsToDelete.Count > 0) { // We need to remove children first to keep consistency in our data var partToDelete = partsToDelete.Peek(); var children = Asset.EnumerateChildPartDesigns(partToDelete, Asset.Hierarchy, false).ToList(); if (children.Count > 0) { // Enqueue children if there is any, and re-process the stack children.ForEach(x => partsToDelete.Push(x)); continue; } // No children to process, we can safely remove the current part from the stack partToDelete = partsToDelete.Pop(); // First remove all references to the part we are deleting // Note: we must do this first so instances of this base will be able to properly make the connection with the base part being cleared var containedIdentifiables = IdentifiableObjectCollector.Collect(Definition, Container.NodeContainer.GetNode(partToDelete.Part)); containedIdentifiables.Keys.ForEach(x => referencesToClear.Add(x)); referencesToClear.Add(partToDelete.Part.Id); // Then actually remove the part from the hierarchy RemovePartFromAsset(partToDelete); // Keep track of deleted part instances if (partToDelete.Base != null) { deletedPartsMapping.Add(Tuple.Create(partToDelete.Base.BasePartId, partToDelete.Base.InstanceId)); } } TrackDeletedInstanceParts(deletedPartsMapping); ClearReferencesToObjects(referencesToClear); }
/// <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); }