コード例 #1
0
            public AnimationBuilderStackEntry(Type type, int startIndex, int endIndex, int operationIndex)
            {
                Type       = type;
                StartIndex = startIndex;
                EndIndex   = endIndex;

                ObjectStartOffset = 0;

                Member         = null;
                LeaveOperation = UpdateOperationType.Invalid;
                LeaveOffset    = 0;

                OperationIndex = operationIndex;
            }
コード例 #2
0
 /// <summary>
 /// Registers a new member for a given type and name.
 /// </summary>
 /// <param name="owner">The owner type.</param>
 /// <param name="name">The member name.</param>
 /// <param name="updatableMember">The member update class to get and set value.</param>
 public static void RegisterMember(Type owner, string name, UpdatableMember updatableMember)
 {
     UpdateKeys[new UpdateKey(owner, name)] = updatableMember;
 }
コード例 #3
0
        private static void ProcessMember(ref ComputeUpdateOperationState state, UpdateMemberInfo animationPath, UpdatableMember updatableMember, List <object> temporaryObjectsList)
        {
            int leaveOffset    = 0;
            var leaveOperation = UpdateOperationType.Invalid;

            // Note: only matters on leaf/leave nodes
            // It will be processed during following PopObjects
            state.LastChildMember = updatableMember;

            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,
                });
            }
        }
コード例 #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 = null;

                            // 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;
                            }

                            var propertyName = animationPath.Name.Substring(propertyStart, state.ParseElementEnd - propertyStart); // TODO: Avoid substring?

                            // try to find a member updater
                            var parentType = containerType;
                            while (parentType != null && !UpdateKeys.TryGetValue(new UpdateKey(parentType, propertyName), out updatableMember))
                            {
                                parentType = parentType.GetTypeInfo().BaseType;
                            }

                            // if not found, try to find a custom resolver
                            parentType = containerType;
                            while (updatableMember == null && 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(),
            });
        }