Example #1
0
        private static void PopObjects(ref ComputeUpdateOperationState state, int desiredStackSize)
        {
            // Leave the objects that are not part of the path anymore
            while (state.StackPath.Count > desiredStackSize)
            {
                // Pop entry
                var stackPathPart = state.StackPath.Last();
                state.StackPath.RemoveAt(state.StackPath.Count - 1);

                // Perform any necessary exit action
                if (stackPathPart.LeaveOperation != UpdateOperationType.Invalid)
                {
                    state.UpdateOperations.Add(new UpdateOperation
                    {
                        Type   = stackPathPart.LeaveOperation,
                        Member = stackPathPart.Member,
                    });

                    // We execute a leave operation, previous stack will be restored
                    state.PreviousOffset = stackPathPart.LeaveOffset;
                }

                // Sets how many operations to skip in case the object we entered was null
                if (stackPathPart.OperationIndex != -1)
                {
                    var updateOperation = state.UpdateOperations[stackPathPart.OperationIndex];
                    updateOperation.SkipCountIfNull = state.UpdateOperations.Count - stackPathPart.OperationIndex - 1;
                    state.UpdateOperations[stackPathPart.OperationIndex] = updateOperation;
                }
            }
        }
Example #2
0
        private static void PopObjects(ref ComputeUpdateOperationState state, int desiredStackSize)
        {
            // Leave the objects that are not part of the path anymore
            while (state.StackPath.Count > desiredStackSize)
            {
                // Pop entry
                var stackPathPart = state.StackPath.Last();
                state.StackPath.RemoveAt(state.StackPath.Count - 1);

                // Perform any necessary exit action
                if (stackPathPart.LeaveOperation != UpdateOperationType.Invalid)
                {
                    state.UpdateOperations.Add(new UpdateOperation
                    {
                        Type   = stackPathPart.LeaveOperation,
                        Member = stackPathPart.Member,
                    });

                    // We execute a leave operation, previous stack will be restored
                    state.PreviousOffset = stackPathPart.LeaveOffset;
                }

                if (stackPathPart.OperationIndex != -1)
                {
                    var updateOperation = state.UpdateOperations[stackPathPart.OperationIndex];

                    // Setup VerifyEnter using last child of current node
                    var verifyEnter = state.LastChildMember?.CreateEnterChecker();
                    if (verifyEnter != null)
                    {
                        updateOperation.EnterChecker = verifyEnter;
                    }

                    // Sets how many operations to skip in case the object we entered was null
                    updateOperation.SkipCountIfNull = state.UpdateOperations.Count - stackPathPart.OperationIndex - 1;
                    state.UpdateOperations[stackPathPart.OperationIndex] = updateOperation;
                }

                // Set last child member to the node we just left
                state.LastChildMember = stackPathPart.Member;
            }
        }
Example #3
0
        private static void ProcessMember(ref ComputeUpdateOperationState state, UpdateMemberInfo animationPath, UpdatableMember updatableMember, List <object> temporaryObjectsList)
        {
            int leaveOffset    = 0;
            var leaveOperation = UpdateOperationType.Invalid;

            var updatableField = updatableMember as UpdatableField;

            if (updatableField != null)
            {
                // Apply field offset
                state.NewOffset += updatableField.Offset;

                if (state.ParseElementEnd == animationPath.Name.Length)
                {
                    // Leaf node, perform the set operation
                    state.UpdateOperations.Add(new UpdateOperation
                    {
                        Type         = updatableField.GetSetOperationType(),
                        Member       = updatableField,
                        AdjustOffset = state.NewOffset - state.PreviousOffset,
                        DataOffset   = animationPath.DataOffset,
                    });
                    state.PreviousOffset = state.NewOffset;
                }
                else if (!updatableField.MemberType.GetTypeInfo().IsValueType)
                {
                    // Only in case of objects we need to enter into them
                    state.UpdateOperations.Add(new UpdateOperation
                    {
                        Type         = UpdateOperationType.EnterObjectField,
                        Member       = updatableField,
                        AdjustOffset = state.NewOffset - state.PreviousOffset,
                    });
                    leaveOperation       = UpdateOperationType.Leave;
                    leaveOffset          = state.NewOffset;
                    state.PreviousOffset = state.NewOffset = 0;
                }
            }
            else
            {
                var updatableProperty = updatableMember as UpdatablePropertyBase;
                if (updatableProperty != null)
                {
                    if (state.ParseElementEnd == animationPath.Name.Length)
                    {
                        // Leaf node, perform the set the value
                        state.UpdateOperations.Add(new UpdateOperation
                        {
                            Type         = updatableProperty.GetSetOperationType(),
                            Member       = updatableProperty,
                            AdjustOffset = state.NewOffset - state.PreviousOffset,
                            DataOffset   = animationPath.DataOffset,
                        });
                        state.PreviousOffset = state.NewOffset;
                    }
                    else
                    {
                        // Otherwise enter into the property
                        bool isStruct             = updatableProperty.MemberType.GetTypeInfo().IsValueType;
                        int  temporaryObjectIndex = -1;

                        if (isStruct)
                        {
                            // Struct properties need a storage area so that we can later set the updated value back into the property
                            leaveOperation       = UpdateOperationType.LeaveAndCopyStructPropertyBase;
                            temporaryObjectIndex = temporaryObjectsList.Count;
                            temporaryObjectsList.Add(Activator.CreateInstance(updatableProperty.MemberType));
                        }
                        else
                        {
                            leaveOperation = UpdateOperationType.Leave;
                        }

                        state.UpdateOperations.Add(new UpdateOperation
                        {
                            Type            = updatableProperty.GetEnterOperationType(),
                            Member          = updatableProperty,
                            AdjustOffset    = state.NewOffset - state.PreviousOffset,
                            DataOffset      = temporaryObjectIndex,
                            SkipCountIfNull = -1,
                        });

                        leaveOffset          = state.NewOffset;
                        state.PreviousOffset = state.NewOffset = 0;
                    }
                }
            }

            // No need to add the last part of the path, as we rarely set and then enter (and if we do we need to reevaluate updated value anyway)
            if (state.ParseElementEnd < animationPath.Name.Length)
            {
                state.StackPath.Add(new AnimationBuilderStackEntry(updatableMember.MemberType, state.ParseElementStart, state.ParseElementEnd, state.UpdateOperations.Count - 1)
                {
                    Member            = updatableMember,
                    LeaveOperation    = leaveOperation,
                    LeaveOffset       = leaveOffset,
                    ObjectStartOffset = state.NewOffset
                });
            }
        }
Example #4
0
        /// <summary>
        /// Compiles a list of update operations into a <see cref="CompiledUpdate"/>, for use with <see cref="Run"/>.
        /// </summary>
        /// <param name="rootObjectType">The type of the root object.</param>
        /// <param name="animationPaths">The different paths and source offsets to use when <see cref="Run"/> is applied.</param>
        /// <returns>A <see cref="CompiledUpdate"/> object that can be used for <see cref="Run"/>.</returns>
        public static CompiledUpdate Compile(Type rootObjectType, List <UpdateMemberInfo> animationPaths)
        {
            var currentPath          = string.Empty;
            var temporaryObjectsList = new List <object>();

            var state = new ComputeUpdateOperationState();

            state.UpdateOperations = new List <UpdateOperation>();
            state.StackPath        = new List <AnimationBuilderStackEntry>
            {
                new AnimationBuilderStackEntry(rootObjectType, 0, 0, -1),
            };

            foreach (var animationPath in animationPaths)
            {
                var commonPathParts = 1;

                // Detect how much of the path is unmodified (note: we start from 1 because root always stay there)
                for (int index = 1; index < state.StackPath.Count; index++)
                {
                    var pathPart = state.StackPath[index];

                    // Check if next path part is the same (first check length then content)
                    if (((animationPath.Name.Length == pathPart.EndIndex) ||
                         (animationPath.Name.Length > pathPart.EndIndex && (animationPath.Name[pathPart.EndIndex] == PathDelimiter || animationPath.Name[pathPart.EndIndex] == PathIndexerOpen))) &&
                        string.Compare(animationPath.Name, pathPart.StartIndex, currentPath, pathPart.StartIndex, pathPart.EndIndex - pathPart.StartIndex, StringComparison.Ordinal) == 0)
                    {
                        commonPathParts++;
                        continue;
                    }

                    break;
                }

                PopObjects(ref state, commonPathParts);

                // Parse the new path parts
                state.ParseElementStart = state.StackPath.Last().EndIndex;

                // Compute offset from start of current object
                state.NewOffset = state.StackPath.Last().ObjectStartOffset;

                while (state.ParseElementStart < animationPath.Name.Length)
                {
                    var containerType = state.StackPath.Last().Type;

                    // We have only two cases for now: array or property/field name
                    bool isIndexerAccess = animationPath.Name[state.ParseElementStart] == PathIndexerOpen;
                    if (isIndexerAccess)
                    {
                        // Parse until end of indexer
                        state.ParseElementEnd = animationPath.Name.IndexOf(PathIndexerClose, state.ParseElementStart + 1);
                        if (state.ParseElementEnd == -1)
                        {
                            throw new InvalidOperationException("Property path parse error: could not find indexer end ']'");
                        }

                        // Include the indexer close
                        state.ParseElementEnd++;

                        // Parse integer
                        // TODO: Avoid substring?
                        var indexerString = animationPath.Name.Substring(state.ParseElementStart + 1, state.ParseElementEnd - state.ParseElementStart - 2);

                        // T[], IList<T>, etc...
                        // Try to resolve using custom resolver
                        UpdatableMember updatableMember = null;
                        var             parentType      = containerType;
                        while (parentType != null)
                        {
                            UpdateMemberResolver resolver;
                            if (MemberResolvers.TryGetValue(parentType, out resolver))
                            {
                                updatableMember = resolver.ResolveIndexer(indexerString);
                                if (updatableMember != null)
                                {
                                    break;
                                }
                            }

                            parentType = parentType.GetTypeInfo().BaseType;
                        }

                        // Try interfaces
                        if (updatableMember == null)
                        {
                            foreach (var implementedInterface in containerType.GetTypeInfo().ImplementedInterfaces)
                            {
                                UpdateMemberResolver resolver;
                                if (MemberResolvers.TryGetValue(implementedInterface, out resolver))
                                {
                                    updatableMember = resolver.ResolveIndexer(indexerString);
                                    if (updatableMember != null)
                                    {
                                        break;
                                    }
                                }
                            }
                        }

                        if (updatableMember == null)
                        {
                            throw new InvalidOperationException(string.Format("Property path parse error: could not find binding info for index {0} in type {1}", indexerString, containerType));
                        }

                        ProcessMember(ref state, animationPath, updatableMember, temporaryObjectsList);
                    }
                    else
                    {
                        // Note: first character might be a . delimiter, if so, skip it
                        var propertyStart = state.ParseElementStart;
                        if (animationPath.Name[propertyStart] == PathDelimiter)
                        {
                            propertyStart++;
                        }

                        // Check if it started with a parenthesis (to perform a cast operation)
                        if (animationPath.Name[propertyStart] == PathCastOpen)
                        {
                            // Parse until end of cast operation
                            state.ParseElementEnd = animationPath.Name.IndexOf(PathCastClose, ++propertyStart);
                            if (state.ParseElementEnd == -1)
                            {
                                throw new InvalidOperationException("Property path parse error: could not find cast operation ending ')'");
                            }

                            var typeName = animationPath.Name.Substring(propertyStart, state.ParseElementEnd - propertyStart);

                            // Include the indexer close
                            state.ParseElementEnd++;

                            // Try to resolve using alias first, then full assembly registry using assembly qualified name
                            var type = DataSerializerFactory.GetTypeFromAlias(typeName) ?? AssemblyRegistry.GetType(typeName);
                            if (type == null)
                            {
                                throw new InvalidOperationException($"Could not resolve type {typeName}");
                            }

                            // Push entry with new type
                            // TODO: Should we actually perform an early castclass and skip if type is incorrect?
                            state.StackPath.Add(new AnimationBuilderStackEntry(type, state.ParseElementStart, state.ParseElementEnd, -1)
                            {
                                ObjectStartOffset = state.NewOffset,
                            });
                        }
                        else
                        {
                            UpdatableMember updatableMember;

                            // Parse until end next group (or end)
                            state.ParseElementEnd = animationPath.Name.IndexOfAny(PathGroupDelimiters, state.ParseElementStart + 1);
                            if (state.ParseElementEnd == -1)
                            {
                                state.ParseElementEnd = animationPath.Name.Length;
                            }

                            // TODO: Avoid substring?
                            var propertyName = animationPath.Name.Substring(propertyStart, state.ParseElementEnd - propertyStart);
                            if (!UpdateKeys.TryGetValue(new UpdateKey(containerType, propertyName), out updatableMember))
                            {
                                // Try to resolve using custom resolver
                                var parentType = containerType;
                                while (parentType != null)
                                {
                                    UpdateMemberResolver resolver;
                                    if (MemberResolvers.TryGetValue(parentType, out resolver))
                                    {
                                        updatableMember = resolver.ResolveProperty(propertyName);
                                        if (updatableMember != null)
                                        {
                                            break;
                                        }
                                    }

                                    parentType = parentType.GetTypeInfo().BaseType;
                                }

                                if (updatableMember == null)
                                {
                                    throw new InvalidOperationException(string.Format("Property path parse error: could not find binding info for member {0} in type {1}", propertyName, containerType));
                                }
                            }

                            // Process member
                            ProcessMember(ref state, animationPath, updatableMember, temporaryObjectsList);
                        }
                    }

                    state.ParseElementStart = state.ParseElementEnd;
                }

                currentPath = animationPath.Name;
            }

            // Totally pop the stack (we might still have stuff to copy back into properties
            PopObjects(ref state, 0);

            return(new CompiledUpdate
            {
                TemporaryObjects = temporaryObjectsList.ToArray(),
                UpdateOperations = state.UpdateOperations.ToArray(),
            });
        }
Example #5
0
        private static void ProcessMember(ref ComputeUpdateOperationState state, UpdateMemberInfo animationPath, UpdatableMember updatableMember, List<object> temporaryObjectsList)
        {
            int leaveOffset = 0;
            var leaveOperation = UpdateOperationType.Invalid;

            var updatableField = updatableMember as UpdatableField;
            if (updatableField != null)
            {
                // Apply field offset
                state.NewOffset += updatableField.Offset;

                if (state.ParseElementEnd == animationPath.Name.Length)
                {
                    // Leaf node, perform the set operation
                    state.UpdateOperations.Add(new UpdateOperation
                    {
                        Type = updatableField.GetSetOperationType(),
                        Member = updatableField,
                        AdjustOffset = state.NewOffset - state.PreviousOffset,
                        DataOffset = animationPath.DataOffset,
                    });
                    state.PreviousOffset = state.NewOffset;
                }
                else if (!updatableField.MemberType.GetTypeInfo().IsValueType)
                {
                    // Only in case of objects we need to enter into them
                    state.UpdateOperations.Add(new UpdateOperation
                    {
                        Type = UpdateOperationType.EnterObjectField,
                        Member = updatableField,
                        AdjustOffset = state.NewOffset - state.PreviousOffset,
                    });
                    leaveOperation = UpdateOperationType.Leave;
                    leaveOffset = state.NewOffset;
                    state.PreviousOffset = state.NewOffset = 0;
                }
            }
            else
            {
                var updatableProperty = updatableMember as UpdatablePropertyBase;
                if (updatableProperty != null)
                {
                    if (state.ParseElementEnd == animationPath.Name.Length)
                    {
                        // Leaf node, perform the set the value
                        state.UpdateOperations.Add(new UpdateOperation
                        {
                            Type = updatableProperty.GetSetOperationType(),
                            Member = updatableProperty,
                            AdjustOffset = state.NewOffset - state.PreviousOffset,
                            DataOffset = animationPath.DataOffset,
                        });
                        state.PreviousOffset = state.NewOffset;
                    }
                    else
                    {
                        // Otherwise enter into the property
                        bool isStruct = updatableProperty.MemberType.GetTypeInfo().IsValueType;
                        int temporaryObjectIndex = -1;

                        if (isStruct)
                        {
                            // Struct properties need a storage area so that we can later set the updated value back into the property
                            leaveOperation = UpdateOperationType.LeaveAndCopyStructPropertyBase;
                            temporaryObjectIndex = temporaryObjectsList.Count;
                            temporaryObjectsList.Add(Activator.CreateInstance(updatableProperty.MemberType));
                        }
                        else
                        {
                            leaveOperation = UpdateOperationType.Leave;
                        }

                        state.UpdateOperations.Add(new UpdateOperation
                        {
                            Type = updatableProperty.GetEnterOperationType(),
                            Member = updatableProperty,
                            AdjustOffset = state.NewOffset - state.PreviousOffset,
                            DataOffset = temporaryObjectIndex,
                            SkipCountIfNull = -1,
                        });

                        leaveOffset = state.NewOffset;
                        state.PreviousOffset = state.NewOffset = 0;
                    }
                }
            }

            // No need to add the last part of the path, as we rarely set and then enter (and if we do we need to reevaluate updated value anyway)
            if (state.ParseElementEnd < animationPath.Name.Length)
            {
                state.StackPath.Add(new AnimationBuilderStackEntry(updatableMember.MemberType, state.ParseElementStart, state.ParseElementEnd, state.UpdateOperations.Count - 1)
                {
                    Member = updatableMember,
                    LeaveOperation = leaveOperation,
                    LeaveOffset = leaveOffset,
                    ObjectStartOffset = state.NewOffset
                });
            }
        }
Example #6
0
        private static void PopObjects(ref ComputeUpdateOperationState state, int desiredStackSize)
        {
            // Leave the objects that are not part of the path anymore
            while (state.StackPath.Count > desiredStackSize)
            {
                // Pop entry
                var stackPathPart = state.StackPath.Last();
                state.StackPath.RemoveAt(state.StackPath.Count - 1);

                // Perform any necessary exit action
                if (stackPathPart.LeaveOperation != UpdateOperationType.Invalid)
                {
                    state.UpdateOperations.Add(new UpdateOperation
                    {
                        Type = stackPathPart.LeaveOperation,
                        Member = stackPathPart.Member,
                    });

                    // We execute a leave operation, previous stack will be restored
                    state.PreviousOffset = stackPathPart.LeaveOffset;
                }

                // Sets how many operations to skip in case the object we entered was null
                if (stackPathPart.OperationIndex != -1)
                {
                    var updateOperation = state.UpdateOperations[stackPathPart.OperationIndex];
                    updateOperation.SkipCountIfNull = state.UpdateOperations.Count - stackPathPart.OperationIndex - 1;
                    state.UpdateOperations[stackPathPart.OperationIndex] = updateOperation;
                }
            }
        }
Example #7
0
        /// <summary>
        /// Compiles a list of update operations into a <see cref="CompiledUpdate"/>, for use with <see cref="Run"/>.
        /// </summary>
        /// <param name="rootObjectType">The type of the root object.</param>
        /// <param name="animationPaths">The different paths and source offsets to use when <see cref="Run"/> is applied.</param>
        /// <returns>A <see cref="CompiledUpdate"/> object that can be used for <see cref="Run"/>.</returns>
        public static CompiledUpdate Compile(Type rootObjectType, List<UpdateMemberInfo> animationPaths)
        {
            var currentPath = string.Empty;
            var temporaryObjectsList = new List<object>();

            var state = new ComputeUpdateOperationState();
            state.UpdateOperations = new List<UpdateOperation>();
            state.StackPath = new List<AnimationBuilderStackEntry>
            {
                new AnimationBuilderStackEntry(rootObjectType, 0, 0, -1),
            };

            foreach (var animationPath in animationPaths)
            {
                var commonPathParts = 1;

                // Detect how much of the path is unmodified (note: we start from 1 because root always stay there)
                for (int index = 1; index < state.StackPath.Count; index++)
                {
                    var pathPart = state.StackPath[index];

                    // Check if next path part is the same (first check length then content)
                    if (((animationPath.Name.Length == pathPart.EndIndex) ||
                         (animationPath.Name.Length > pathPart.EndIndex && (animationPath.Name[pathPart.EndIndex] == PathDelimiter || animationPath.Name[pathPart.EndIndex] == PathIndexerOpen)))
                        && string.Compare(animationPath.Name, pathPart.StartIndex, currentPath, pathPart.StartIndex, pathPart.EndIndex - pathPart.StartIndex, StringComparison.Ordinal) == 0)
                    {
                        commonPathParts++;
                        continue;
                    }

                    break;
                }

                PopObjects(ref state, commonPathParts);

                // Parse the new path parts
                state.ParseElementStart = state.StackPath.Last().EndIndex;

                // Compute offset from start of current object
                state.NewOffset = state.StackPath.Last().ObjectStartOffset;

                while (state.ParseElementStart < animationPath.Name.Length)
                {
                    var containerType = state.StackPath.Last().Type;

                    // We have only two cases for now: array or property/field name
                    bool isIndexerAccess = animationPath.Name[state.ParseElementStart] == PathIndexerOpen;
                    if (isIndexerAccess)
                    {
                        // Parse until end of indexer
                        state.ParseElementEnd = animationPath.Name.IndexOf(PathIndexerClose, state.ParseElementStart + 1);
                        if (state.ParseElementEnd == -1)
                            throw new InvalidOperationException("Property path parse error: could not find indexer end ']'");

                        // Include the indexer close
                        state.ParseElementEnd++;

                        // Parse integer
                        // TODO: Avoid substring?
                        var indexerString = animationPath.Name.Substring(state.ParseElementStart + 1, state.ParseElementEnd - state.ParseElementStart - 2);

                        // T[], IList<T>, etc...
                        // Try to resolve using custom resolver
                        UpdatableMember updatableMember = null;
                        var parentType = containerType;
                        while (parentType != null)
                        {
                            UpdateMemberResolver resolver;
                            if (MemberResolvers.TryGetValue(parentType, out resolver))
                            {
                                updatableMember = resolver.ResolveIndexer(indexerString);
                                if (updatableMember != null)
                                    break;
                            }

                            parentType = parentType.GetTypeInfo().BaseType;
                        }

                        // Try interfaces
                        if (updatableMember == null)
                        {
                            foreach (var implementedInterface in containerType.GetTypeInfo().ImplementedInterfaces)
                            {
                                UpdateMemberResolver resolver;
                                if (MemberResolvers.TryGetValue(implementedInterface, out resolver))
                                {
                                    updatableMember = resolver.ResolveIndexer(indexerString);
                                    if (updatableMember != null)
                                        break;
                                }
                            }
                        }

                        if (updatableMember == null)
                        {
                            throw new InvalidOperationException(string.Format("Property path parse error: could not find binding info for index {0} in type {1}", indexerString, containerType));
                        }

                        ProcessMember(ref state, animationPath, updatableMember, temporaryObjectsList);
                    }
                    else
                    {
                        // Note: first character might be a . delimiter, if so, skip it
                        var propertyStart = state.ParseElementStart;
                        if (animationPath.Name[propertyStart] == PathDelimiter)
                            propertyStart++;

                        // Check if it started with a parenthesis (to perform a cast operation)
                        if (animationPath.Name[propertyStart] == PathCastOpen)
                        {
                            // Parse until end of cast operation
                            state.ParseElementEnd = animationPath.Name.IndexOf(PathCastClose, ++propertyStart);
                            if (state.ParseElementEnd == -1)
                                throw new InvalidOperationException("Property path parse error: could not find cast operation ending ')'");

                            var typeName = animationPath.Name.Substring(propertyStart, state.ParseElementEnd - propertyStart);

                            // Include the indexer close
                            state.ParseElementEnd++;

                            // Try to resolve using alias first, then full assembly registry using assembly qualified name
                            var type = DataSerializerFactory.GetTypeFromAlias(typeName) ?? AssemblyRegistry.GetType(typeName);
                            if (type == null)
                            {
                                throw new InvalidOperationException($"Could not resolve type {typeName}");
                            }

                            // Push entry with new type
                            // TODO: Should we actually perform an early castclass and skip if type is incorrect?
                            state.StackPath.Add(new AnimationBuilderStackEntry(type, state.ParseElementStart, state.ParseElementEnd, -1)
                            {
                                ObjectStartOffset = state.NewOffset,
                            });
                        }
                        else
                        {
                            UpdatableMember updatableMember;

                            // Parse until end next group (or end)
                            state.ParseElementEnd = animationPath.Name.IndexOfAny(PathGroupDelimiters, state.ParseElementStart + 1);
                            if (state.ParseElementEnd == -1)
                                state.ParseElementEnd = animationPath.Name.Length;

                            // TODO: Avoid substring?
                            var propertyName = animationPath.Name.Substring(propertyStart, state.ParseElementEnd - propertyStart);
                            if (!UpdateKeys.TryGetValue(new UpdateKey(containerType, propertyName), out updatableMember))
                            {
                                // Try to resolve using custom resolver
                                var parentType = containerType;
                                while (parentType != null)
                                {
                                    UpdateMemberResolver resolver;
                                    if (MemberResolvers.TryGetValue(parentType, out resolver))
                                    {
                                        updatableMember = resolver.ResolveProperty(propertyName);
                                        if (updatableMember != null)
                                            break;
                                    }

                                    parentType = parentType.GetTypeInfo().BaseType;
                                }

                                if (updatableMember == null)
                                {
                                    throw new InvalidOperationException(string.Format("Property path parse error: could not find binding info for member {0} in type {1}", propertyName, containerType));
                                }
                            }

                            // Process member
                            ProcessMember(ref state, animationPath, updatableMember, temporaryObjectsList);
                        }
                    }

                    state.ParseElementStart = state.ParseElementEnd;
                }

                currentPath = animationPath.Name;
            }

            // Totally pop the stack (we might still have stuff to copy back into properties
            PopObjects(ref state, 0);

            return new CompiledUpdate
            {
                TemporaryObjects = temporaryObjectsList.ToArray(),
                UpdateOperations = state.UpdateOperations.ToArray(),
            };
        }