public static string DiffingHash(this object obj, DiffConfig diffConfig = null)
        {
            diffConfig = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();

            // The following is to consider only the PropertiesToInclude specified in the diffConfig.
            // Since the SHA hash algorithm can only consider "exceptions", we need to retrieve all the top level properties,
            // intersect them with the set of PropertiesToInclude, and treat all the properties that remain out as "exceptions" (not to be considered).
            if (diffConfig.PropertiesToConsider.Any())
            {
                IEnumerable <string> exceptions = BH.Engine.Reflection.Query.PropertyNames(obj).Except(diffConfig.PropertiesToConsider);
                diffConfig.PropertiesToIgnore.AddRange(exceptions);
            }

            // The current Hash must not be considered when computing the hash. Remove HashFragment if present.
            IBHoMObject bhomobj = obj as IBHoMObject;

            if (bhomobj != null)
            {
                bhomobj = BH.Engine.Base.Query.DeepClone(obj) as IBHoMObject;
                bhomobj.Fragments.Remove(typeof(HashFragment));
                return(Compute.SHA256Hash(bhomobj, diffConfig.PropertiesToIgnore));
            }

            return(Compute.SHA256Hash(obj, diffConfig.PropertiesToIgnore));
        }
Exemple #2
0
        // Computes the diffing for IEnumerable<object>.
        // For BHoMObjects, it assumes that they all have a HashFragment assigned (like when they have been passed through a Revision).
        // For non-BHoMObjects, it performs the VennDiagram comparision with a HashComparer.
        // Results for BHoMObjects and non are concatenated.
        private static Diff DiffRevisionObjects(IEnumerable <object> pastRevisionObjs, IEnumerable <object> followingRevisionObjs, DiffConfig diffConfig = null)
        {
            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : diffConfig.DeepClone() as DiffConfig;

            // Dispatch the objects in BHoMObjects and generic objects.
            IEnumerable <IBHoMObject> prevObjs_BHoM = pastRevisionObjs.OfType <IBHoMObject>();
            IEnumerable <IBHoMObject> currObjs_BHoM = followingRevisionObjs.OfType <IBHoMObject>();

            // If all objects are bhomobjects, just call the appropriate method
            if (pastRevisionObjs.Count() != 0 && pastRevisionObjs.Count() == prevObjs_BHoM.Count() && followingRevisionObjs.Count() == currObjs_BHoM.Count())
            {
                return(DiffRevisionObjects(prevObjs_BHoM, currObjs_BHoM, diffConfigCopy));
            }

            IEnumerable <object> prevObjs_nonBHoM = pastRevisionObjs.Where(o => !(o is IBHoMObject));
            IEnumerable <object> currObjs_nonBHoM = followingRevisionObjs.Where(o => !(o is IBHoMObject));

            // Compute the specific Diffing for the BHoMObjects.
            Diff diff = Compute.DiffRevisionObjects(prevObjs_BHoM, currObjs_BHoM, diffConfigCopy);

            // Compute the generic Diffing for the other objects.
            // This is left to the VennDiagram with a HashComparer.
            VennDiagram <object> vd = Engine.Data.Create.VennDiagram(prevObjs_nonBHoM, currObjs_nonBHoM, new DiffingHashComparer <object>(diffConfig));

            // Concatenate the results of the two diffing operations.
            List <object> allPrevObjs      = new List <object>();
            List <object> allCurrObjs      = new List <object>();
            List <object> allUnchangedObjs = new List <object>();

            allCurrObjs.AddRange(diff.AddedObjects);
            allCurrObjs.AddRange(vd.OnlySet1);

            allPrevObjs.AddRange(diff.RemovedObjects);
            allPrevObjs.AddRange(vd.OnlySet2);

            // Create the final, actual diff.
            Diff finalDiff = new Diff(allCurrObjs, allPrevObjs, diff.ModifiedObjects, diffConfigCopy, diff.ModifiedPropsPerObject, diff.UnchangedObjects);

            return(finalDiff);
        }
        public static Diff DiffGenericObjects(IEnumerable <object> pastObjects, IEnumerable <object> currentObjects, DiffConfig diffConfig = null, bool useExistingHash = false)
        {
            BH.Engine.Reflection.Compute.RecordNote("This diffing method cannot track modified objects between different revisions." +
                                                    "\nIt will simply return the objects that appear exclusively in the past set, in the following set, and in both." +
                                                    $"\nConsider using '{nameof(DiffWithCustomId)}', '{nameof(DiffWithFragmentId)}' or '{nameof(DiffRevisions)}' if this feature is needed.");

            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();

            // Clone objects for immutability in the UI.
            List <object> pastObjects_cloned    = BH.Engine.Base.Query.DeepClone(pastObjects).ToList();
            List <object> currentObjects_cloned = BH.Engine.Base.Query.DeepClone(currentObjects).ToList();

            if (!useExistingHash)
            {
                // Clean any existing hash fragment.
                // This ensures the hash will be re-computed within this method using the provided DiffConfig.
                pastObjects_cloned.OfType <IBHoMObject>().ToList().ForEach(o => o.Fragments.Remove(typeof(HashFragment)));
                currentObjects_cloned.OfType <IBHoMObject>().ToList().ForEach(o => o.Fragments.Remove(typeof(HashFragment)));
            }

            // Compute the "Diffing" by means of a VennDiagram.
            // Hashes are computed in the DiffingHashComparer, once per each object (the hash is stored in a hashFragment).
            VennDiagram <object> vd = Engine.Data.Create.VennDiagram(pastObjects_cloned, currentObjects_cloned, new DiffingHashComparer <object>(diffConfigCopy, true));

            return(new Diff(vd.OnlySet2, vd.OnlySet1, null, diffConfigCopy, null, vd.Intersection));
        }
        public static Diff DiffOneByOne(IEnumerable <object> pastObjects, IEnumerable <object> currentObjects, DiffConfig diffConfig = null, bool useExistingHash = false)
        {
            if (pastObjects.Count() != currentObjects.Count())
            {
                BH.Engine.Reflection.Compute.RecordWarning($"Input collections must be of the same length for '{nameof(DiffOneByOne)}' to work.");
                return(null);
            }

            BH.Engine.Reflection.Compute.RecordNote($"This diffing method is equivalent to Equivalent to calling '{nameof(Query.DifferentProperties)}' on the input lists. " +
                                                    $"\nThis will only identify 'modified' or 'unchanged' objects. For 'modified' objects, the property differences are also returned." +
                                                    $"\nIt will work correctly only if the objects in the lists are in the same order and at most they have been modified (i.e. no new object has been added, no object has been deleted).");

            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();

            diffConfigCopy.EnablePropertyDiffing = true; // must be forced on for this Diffing method to make sense.

            // Clone objects for immutability in the UI.
            List <object> pastObjects_cloned    = BH.Engine.Base.Query.DeepClone(pastObjects).ToList();
            List <object> currentObjects_cloned = BH.Engine.Base.Query.DeepClone(currentObjects).ToList();

            List <object> modifiedObjects  = new List <object>();
            List <object> unchangedObjects = new List <object>();

            bool anyChangeDetected = false;

            var allModifiedProps = new Dictionary <string, Dictionary <string, Tuple <object, object> > >();

            for (int i = 0; i < pastObjects_cloned.Count(); i++)
            {
                var modifiedProps = Query.DifferentProperties(currentObjects_cloned[i], pastObjects_cloned[i], diffConfigCopy);

                if (modifiedProps != null && modifiedProps.Any())
                {
                    modifiedObjects.Add(currentObjects_cloned[i]);
                    anyChangeDetected = true;
                }
                else if (diffConfig.StoreUnchangedObjects)
                {
                    unchangedObjects.Add(currentObjects_cloned[i]);
                }

                allModifiedProps[$"Object #{i}"] = modifiedProps ?? new Dictionary <string, Tuple <object, object> >();
            }

            if (!anyChangeDetected)
            {
                allModifiedProps = null;
            }

            return(new Diff(new List <object>(), new List <object>(), modifiedObjects, diffConfigCopy, allModifiedProps, unchangedObjects));
        }
        public static Diff Diffing(IEnumerable <object> pastObjs, IEnumerable <object> followingObjs, DiffConfig diffConfig = null, string customDataIdKey = null)
        {
            if (!pastObjs.Any() || !followingObjs.Any())
            {
                BH.Engine.Reflection.Compute.RecordWarning("No input objects provided.");
                return(null);
            }

            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();

            if (pastObjs.Count() == 1 && followingObjs.Count() == 1)
            {
                Revision pastRev = pastObjs.First() as Revision;
                Revision follRev = followingObjs.First() as Revision;

                if (pastRev != null && follRev != null)
                {
                    BH.Engine.Reflection.Compute.RecordNote($"Calling the diffing method '{nameof(DiffRevisions)}'.");

                    if (!string.IsNullOrWhiteSpace(customDataIdKey))
                    {
                        BH.Engine.Reflection.Compute.RecordWarning($"The input {customDataIdKey} is not considered when the input objects are both of type {nameof(Revision)}.");
                    }

                    return(DiffRevisions(pastRev, follRev, diffConfigCopy));
                }
            }

            IEnumerable <IBHoMObject> bHoMObjects_past      = pastObjs.OfType <IBHoMObject>();
            IEnumerable <IBHoMObject> bHoMObjects_following = followingObjs.OfType <IBHoMObject>();

            if (!string.IsNullOrWhiteSpace(customDataIdKey))
            {
                if (bHoMObjects_past.Count() == pastObjs.Count() && bHoMObjects_following.Count() == followingObjs.Count())
                {
                    BH.Engine.Reflection.Compute.RecordNote($"Calling the diffing method '{nameof(DiffWithCustomId)}'.");
                    return(DiffWithCustomId(bHoMObjects_past, bHoMObjects_following, customDataIdKey, diffConfigCopy));
                }
                else
                {
                    BH.Engine.Reflection.Compute.RecordWarning($"To perform the diffing based on an Id stored in the Custom Data, the inputs must be collections of IBHoMObjects.");
                }
            }

            // Check if the BHoMObjects all have a hashfragment assigned.
            // If so, we may attempt the Revision diffing.
            if (bHoMObjects_past.AllHaveHashFragment() && bHoMObjects_following.AllHaveHashFragment())
            {
                BH.Engine.Reflection.Compute.RecordNote($"Calling the diffing method '{nameof(DiffRevisionObjects)}'.");
                return(DiffRevisionObjects(pastObjs, followingObjs, diffConfigCopy));
            }

            if (diffConfigCopy.AllowOneByOneDiffing && pastObjs.Count() == followingObjs.Count())
            {
                BH.Engine.Reflection.Compute.RecordNote($"Calling the diffing method '{nameof(DiffOneByOne)}'" +
                                                        $"\nThis will only identify 'modified' or 'unchanged' objects. It will work correctly only if the input objects are in the same order.");

                return(DiffOneByOne(pastObjs, followingObjs, diffConfigCopy));
            }


            BH.Engine.Reflection.Compute.RecordNote($"Calling the most generic Diffing method, '{nameof(DiffGenericObjects)}'." +
                                                    $"\nThis will only identify new/deleted objects; it will not track which object was modified." +
                                                    $"\nReason: the inputs do not satisfy any of the following conditions (at least one is needed to trigger another more detailed diffing):" +
                                                    $"\n\t* Not all BHoMObjects have a HashFragment assigned (they didn't pass through a Revision);" +
                                                    $"\n\t* No {nameof(customDataIdKey)} was input." +
                                                    $"\n\t* The input collections have different legths.");
            return(DiffGenericObjects(pastObjs as dynamic, followingObjs as dynamic, diffConfigCopy));
        }
Exemple #6
0
        // Computes the Diffing for BHoMObjects that all have a HashFragment assigned (like when they have been passed through a Revision).
        private static Diff DiffRevisionObjects(IEnumerable <IBHoMObject> pastObjects, IEnumerable <IBHoMObject> currentObjects, DiffConfig diffConfig = null)
        {
            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : diffConfig.DeepClone() as DiffConfig;

            // Take the Revision's objects
            List <IBHoMObject> currentObjs = currentObjects.ToList();
            List <IBHoMObject> readObjs    = pastObjects.ToList();

            // Make dictionary with object hashes to speed up the next lookups
            Dictionary <string, IBHoMObject> readObjs_dict = readObjs.ToDictionary(obj => obj.GetHashFragment().CurrentHash, obj => obj);

            // Dispatch the objects: new, modified or old
            List <IBHoMObject> newObjs      = new List <IBHoMObject>();
            List <IBHoMObject> modifiedObjs = new List <IBHoMObject>();
            List <IBHoMObject> oldObjs      = new List <IBHoMObject>();
            List <IBHoMObject> unChanged    = new List <IBHoMObject>();

            var objModifiedProps = new Dictionary <string, Dictionary <string, Tuple <object, object> > >();

            foreach (var obj in currentObjs)
            {
                var hashFragm = obj.GetHashFragment();

                if (hashFragm?.PreviousHash == null)
                {
                    newObjs.Add(obj); // It's a new object
                }

                else if (hashFragm.PreviousHash == hashFragm.CurrentHash)
                {
                    // It's NOT been modified
                    if (diffConfigCopy.StoreUnchangedObjects)
                    {
                        unChanged.Add(obj);
                    }
                }

                else if (hashFragm.PreviousHash != hashFragm.CurrentHash)
                {
                    modifiedObjs.Add(obj); // It's been modified

                    if (diffConfigCopy.EnablePropertyDiffing)
                    {
                        // Determine changed properties
                        IBHoMObject oldObjState = null;
                        readObjs_dict.TryGetValue(hashFragm.PreviousHash, out oldObjState);

                        if (oldObjState == null)
                        {
                            continue;
                        }

                        var differentProps = Query.DifferentProperties(obj, oldObjState, diffConfigCopy);

                        objModifiedProps.Add(hashFragm.CurrentHash, differentProps);
                    }
                }
                else
                {
                    throw new Exception("Could not find hash information to perform Diffing on some objects.");
                }
            }

            // If no modified property was found, set the field to null (otherwise will get empty list)
            objModifiedProps = objModifiedProps.Count == 0 ? null : objModifiedProps;

            // All ReadObjs that cannot be found by hash in the previousHash of the CurrentObjs are toBeDeleted
            Dictionary <string, IBHoMObject> CurrentObjs_withPreviousHash_dict = currentObjs
                                                                                 .Where(obj => obj.GetHashFragment().PreviousHash != null)
                                                                                 .ToDictionary(obj => obj.GetHashFragment().PreviousHash, obj => obj);

            oldObjs = readObjs_dict.Keys.Except(CurrentObjs_withPreviousHash_dict.Keys)
                      .Where(k => readObjs_dict.ContainsKey(k)).Select(k => readObjs_dict[k]).ToList();

            return(new Diff(newObjs, oldObjs, modifiedObjs, diffConfig, objModifiedProps, unChanged));
        }
Exemple #7
0
        public static Diff DiffWithFragmentId(IEnumerable <IBHoMObject> pastObjects, IEnumerable <IBHoMObject> currentObjects, Type fragmentType, string fragmentIdProperty, DiffConfig diffConfig = null)
        {
            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();

            if (string.IsNullOrWhiteSpace(fragmentIdProperty))
            {
                BH.Engine.Reflection.Compute.RecordError($"The DiffConfig must specify a valid {nameof(fragmentIdProperty)}.");
                return(null);
            }

            // Clone for immutability
            List <IBHoMObject> currentObjs = currentObjects.ToList();
            List <IBHoMObject> pastObjs    = pastObjects.ToList();

            string customDataIdKey = fragmentType.Name + "_fragmentId";

            currentObjs.ForEach(o => o.CustomData[customDataIdKey] = o.GetIdFromFragment(fragmentType, fragmentIdProperty));
            pastObjs.ForEach(o => o.CustomData[customDataIdKey]    = o.GetIdFromFragment(fragmentType, fragmentIdProperty));

            return(DiffWithCustomId(pastObjs, currentObjs, customDataIdKey, diffConfig));
        }
Exemple #8
0
        public static Dictionary <string, Tuple <object, object> > DifferentProperties(this object obj1, object obj2, DiffConfig diffConfig = null)
        {
            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : diffConfig.DeepClone() as DiffConfig;

            object obj1Copy = obj1.DeepClone();
            object obj2Copy = obj2.DeepClone();

            var dict = new Dictionary <string, Tuple <object, object> >();

            CompareLogic comparer = new CompareLogic();

            // General configurations.
            comparer.Config.MaxDifferences  = diffConfigCopy.MaxPropertyDifferences;
            comparer.Config.DoublePrecision = diffConfigCopy.NumericTolerance;

            // Set the properties to be ignored.
            if (!diffConfigCopy.PropertiesToIgnore.Contains("BHoM_Guid"))
            {
                diffConfigCopy.PropertiesToIgnore.Add("BHoM_Guid");
            }
            // the above should be replaced by BH.Engine.Reflection.Compute.RecordWarning($"`BHoM_Guid` should generally be ignored when computing the diffing. Consider adding it to the {nameof(diffConfig.PropertiesToIgnore)}.");
            // when the bug in the auto Create() method ("auto-property initialisers for ByRef values like lists do not populate default values") is resolved.

            comparer.Config.MembersToIgnore = diffConfigCopy.PropertiesToIgnore;

            // Removes the CustomData to be ignored.
            var bhomobj1 = (obj1Copy as IBHoMObject);
            var bhomobj2 = (obj2Copy as IBHoMObject);

            if (bhomobj1 != null)
            {
                diffConfig.CustomDataToIgnore.ForEach(k => bhomobj1.CustomData.Remove(k));
                obj1Copy = bhomobj1;
            }

            if (bhomobj2 != null)
            {
                diffConfig.CustomDataToIgnore.ForEach(k => bhomobj2.CustomData.Remove(k));
                obj2Copy = bhomobj2;
            }

            // Never include the changes in HashFragment.
            comparer.Config.TypesToIgnore.Add(typeof(HashFragment));

            // Perform the comparison.
            ComparisonResult result = comparer.Compare(obj1Copy, obj2Copy);

            // Parse and store the differnces as appropriate.
            foreach (var difference in result.Differences)
            {
                string propertyName = difference.PropertyName;

                //workaround for Revit's parameters in Fragments
                if (propertyName.Contains("Fragments") && propertyName.Contains("Parameter") && propertyName.Contains("Value"))
                {
                    propertyName = BH.Engine.Reflection.Query.PropertyValue(difference.ParentObject2, "Name").ToString();
                }

                if (propertyName.Contains("CustomData") && propertyName.Contains("Value"))
                {
                    var splittedName = difference.PropertyName.Split('.');

                    int idx = 0;
                    Int32.TryParse(string.Join(null, System.Text.RegularExpressions.Regex.Split(splittedName.ElementAtOrDefault(1), "[^\\d]")), out idx);

                    string keyName = (obj2Copy as IBHoMObject)?.CustomData.ElementAtOrDefault(idx - 1).Key; // this seems buggy ATM.

                    propertyName = splittedName.FirstOrDefault() + $"['{keyName}']." + splittedName.Last();
                }

                if (!diffConfig.PropertiesToConsider.Any() || diffConfig.PropertiesToConsider.Contains(difference.PropertyName))
                {
                    dict[propertyName] = new Tuple <object, object>(difference.Object1, difference.Object2);
                }
            }

            if (dict.Count == 0)
            {
                return(null);
            }

            return(dict); // this Dictionary may be exploded in the UI by using the method "ListDifferentProperties".
        }
        public static Diff DiffWithCustomId(IEnumerable <IBHoMObject> pastObjects, IEnumerable <IBHoMObject> currentObjects, string customdataIdKey, DiffConfig diffConfig = null)
        {
            // Set configurations if diffConfig is null. Clone it for immutability in the UI.
            DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();

            HashSet <string> currentObjectsIds = new HashSet <string>();
            HashSet <string> pastObjectsIds    = new HashSet <string>();

            // Verifies inputs and populates the id lists.
            ProcessObjectsForDiffing(pastObjects, currentObjects, customdataIdKey, out currentObjectsIds, out pastObjectsIds);

            // Actual diffing
            // Clone for immutability in the UI
            List <IBHoMObject> currentObjs = currentObjects.ToList();
            List <IBHoMObject> pastObjs    = pastObjects.ToList();

            // Make dictionary with object ids to speed up the next lookups
            Dictionary <string, IBHoMObject> currObjs_dict = currentObjectsIds.Zip(currentObjs, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v);
            Dictionary <string, IBHoMObject> pastObjs_dict = pastObjectsIds.Zip(pastObjs, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v);

            // Dispatch the objects: new, modified or deleted
            List <IBHoMObject> newObjs      = new List <IBHoMObject>();
            List <IBHoMObject> modifiedObjs = new List <IBHoMObject>();
            List <IBHoMObject> deletedObjs  = new List <IBHoMObject>();
            List <IBHoMObject> unChanged    = new List <IBHoMObject>();

            var objModifiedProps = new Dictionary <string, Dictionary <string, Tuple <object, object> > >();

            foreach (var kv_curr in currObjs_dict)
            {
                IBHoMObject currentObj   = kv_curr.Value;
                string      currentObjID = kv_curr.Key;

                // Try to find an object between the pastObjs that has the same ID of the current one.
                IBHoMObject correspondingObj = null;
                pastObjs_dict.TryGetValue(kv_curr.Key, out correspondingObj);

                // If none is found, the current object is new.
                if (correspondingObj == null)
                {
                    newObjs.Add(kv_curr.Value);
                    continue;
                }

                // Otherwise, the current object existed in the past set.

                if (diffConfig.EnablePropertyDiffing)
                {
                    // Determine changed properties
                    var differentProps = Query.DifferentProperties(currentObj, correspondingObj, diffConfigCopy);

                    if (differentProps != null && differentProps.Count > 0)
                    {
                        // It's been modified
                        modifiedObjs.Add(currentObj);
                        objModifiedProps.Add(currentObjID, differentProps);
                    }
                    else
                    {
                        // It's NOT been modified
                        if (diffConfigCopy.StoreUnchangedObjects)
                        {
                            unChanged.Add(currentObj);
                        }
                    }
                }
            }

            // If no modified property was found, set the field to null (otherwise will get empty list)
            objModifiedProps = objModifiedProps.Count == 0 ? null : objModifiedProps;

            // All PastObjects that cannot be found by id in the CurrentObjs are old.
            deletedObjs = pastObjs_dict.Keys.Except(currObjs_dict.Keys)
                          .Select(k => pastObjs_dict[k]).ToList();

            if (!newObjs.Any() && !deletedObjs.Any() && !modifiedObjs.Any())
            {
                BH.Engine.Reflection.Compute.RecordWarning($"No difference could be found." +
                                                           $"\nThe provided Id of the objects were completely different between {nameof(pastObjects)} and {nameof(currentObjects)}." +
                                                           $"\nPlease make sure that:" +
                                                           $"\n\t * The input objects constitute the entirety of the model that changed between revisions;" +
                                                           $"\n\t * the input objects come from models that were not completely re-created between revisions.");
            }
            else if (!diffConfig.EnablePropertyDiffing)
            {
                BH.Engine.Reflection.Compute.RecordWarning($"For this Diffing method to detect modified/unchanged objects, you need to set '{nameof(DiffConfig.EnablePropertyDiffing)}' to true in the DiffConfig.");
            }

            return(new Diff(newObjs, deletedObjs, modifiedObjs, diffConfigCopy, objModifiedProps, unChanged));
        }