public override void ApplySnapshot(INode node, IMap <string, S> snapshot)
        {
            StateTreeUtils.Typecheck(this, snapshot);

            var map = GetValue(node);

            var keysmap = map.Keys.Aggregate(new Map <string, bool>(), (acc, key) =>
            {
                acc[key] = false;
                return(acc);
            });

            foreach (var pair in snapshot)
            {
                map[pair.Key]     = SubType.Create(pair.Value, Node.Environment);
                keysmap[pair.Key] = true;
            }

            foreach (var pair in keysmap)
            {
                if (!pair.Value)
                {
                    map.Remove(pair.Key);
                }
            }
        }
        public static INode ValueAsNode <Sx, Tx>(IType <Sx, Tx> type, ObjectNode parent, string subpath, object value, INode oldNode)
        {
            StateTreeUtils.Typecheck(type, value);

            // the new value has a MST node
            if (value.IsStateTreeNode())
            {
                var node = value.GetStateTreeNode();

                node.AssertAlive();

                // the node lives here
                if (node.Parent != null && node.Parent == parent)
                {
                    node.SetParent(parent, subpath);

                    if (oldNode != null && oldNode != node)
                    {
                        oldNode.Dispose();
                    }
                    return(node);
                }
            }

            // there is old node and new one is a value/snapshot
            if (oldNode != null)
            {
                var node = type.Reconcile(oldNode, value);
                node.SetParent(parent, subpath);
                return(node);
            }

            // nothing to do, create from scratch
            return(type.Instantiate(parent, subpath, parent.Environment, value));
        }
        protected override IValidationError[] IsValidSnapshot(object values, IContextEntry[] context)
        {
            if (values is IEnumerable enumerable)
            {
                IList <object> list = new List <object>();

                foreach (var item in enumerable)
                {
                    list.Add(item);
                }

                var errors = list.Select((value, index) => SubType.Validate(value, StateTreeUtils.GetContextForPath(context, $"{index}", SubType)));

                return(errors.Aggregate(Array.Empty <IValidationError>(), (acc, value) => acc.Concat(value).ToArray()));
            }

            return(new IValidationError[]
            {
                new ValidationError
                {
                    Context = context,

                    Value = values,

                    Message = $"Value is not an array or list or enumerable"
                }
            });
        }
Beispiel #4
0
        private S GetDefaultValue()
        {
            var defaultValue = _DefaultValue != null?_DefaultValue() : default(S);

            StateTreeUtils.Typecheck(_Type, defaultValue);

            return(defaultValue);
        }
Beispiel #5
0
        public T Create(S snapshot = default(S), IEnvironment environment = null)
        {
            snapshot = snapshot != null ? snapshot : GetDefaultSnapshot();

            StateTreeUtils.Typecheck(this, snapshot);

            return((T)Instantiate(null, "", environment, snapshot).Value);
        }
        public override void ApplySnapshot(INode node, S[] snapshot)
        {
            StateTreeUtils.Typecheck(this, snapshot);

            var values = snapshot.Select(snap => SubType.Create(snap, node.Environment)).ToArray();

            GetValue(node).Replace(values);
        }
        public override void ApplySnapshot(INode node, S snapshot)
        {
            var value = (S)ApplySnapshotPreProcessor(snapshot);

            StateTreeUtils.Typecheck(this, value);

            if (node.StoredValue is IObservableObject <T, INode> observable)
            {
                foreach (var property in PropertyNames)
                {
                    observable.Write(property, GetPropertyValue(value, property));
                }
            }
        }
        private IObjectWillChange WillChange(IObjectWillChange change)
        {
            var node = change.Object.GetStateTreeNode();

            node.AssertWritable();

            // only properties are typed, state are stored as-is references
            var type = Properties.ContainsKey(change.Name) ? Properties[change.Name] : null;

            if (type != null)
            {
                StateTreeUtils.Typecheck(type, change.NewValue);

                change.NewValue = type.Reconcile(node.GetChildNode(change.Name), change.NewValue);
            }

            return(change);
        }
        public static IDisposable OnAction(this object target, Action <ISerializedActionCall> listener, bool attachAfter = true)
        {
            if (!target.IsStateTreeNode())
            {
                throw new InvalidOperationException("Can not listen for action on Non State Tree Node");
            }

            void FireListener(IMiddlewareEvent call)
            {
                if (call.Type == MiddlewareEventType.Action && call.Id == call.RootId)
                {
                    var source = call.Context.GetStateTreeNode();

                    var data = new SerializedActionCall
                    {
                        Name = call.Name,

                        Path = StateTreeUtils.GetRelativePathBetweenNodes(target.GetStateTreeNode(), source),

                        //TODO: serialize arguments
                        Arguments = call.Arguments.ToArray()
                    };

                    listener(data);
                }
            }

            Action <IMiddlewareEvent, Action <IMiddlewareEvent, Func <object, object> >, Action <object> > onAfterAction = (IMiddlewareEvent call, Action <IMiddlewareEvent, Func <object, object> > next, Action <object> action) =>
            {
                next(call, null);

                FireListener(call);
            };

            Action <IMiddlewareEvent, Action <IMiddlewareEvent, Func <object, object> >, Action <object> > onBeforeAction = (IMiddlewareEvent call, Action <IMiddlewareEvent, Func <object, object> > next, Action <object> action) =>
            {
                FireListener(call);

                next(call, null);
            };

            return(StateTreeAction.AddMiddleware(target.GetStateTree(), attachAfter ? onAfterAction : onBeforeAction));
        }
Beispiel #10
0
        private IListWillChange <INode> WillChange(IListWillChange <INode> change)
        {
            var node = change.Object.GetStateTreeNode();

            node.AssertWritable();

            var childNodes = node.GetChildren().ToList();

            switch (change.Type)
            {
            case ChangeType.UPDATE:
            {
                if (change.NewValue == childNodes[change.Index])
                {
                    return(null);
                }
                change.NewValue = StateTreeUtils.ReconcileListItems(SubType, node,
                                                                    new INode[] { childNodes[change.Index] }.ToList(),
                                                                    new object[] { change.NewValue },
                                                                    new string[] { $"{change.Index}" })[0];
            }
            break;

            case ChangeType.SPLICE:
            {
                change.Added = StateTreeUtils.ReconcileListItems(SubType, node,
                                                                 childNodes.Slice(change.Index, change.Index + change.RemovedCount),
                                                                 change.Added,
                                                                 change.Added.Select((added, index) => $"{change.Index + index}").ToArray())
                               .ToArray();

                // update paths of remaining items
                for (int i = change.Index + change.RemovedCount; i < childNodes.Count; i++)
                {
                    childNodes[i].SetParent(node, $"{i + change.Added.Length - change.RemovedCount}");
                }
            }
            break;
            }

            return(change);
        }
        private IMapWillChange <string, INode> WillChange(IMapWillChange <string, INode> change)
        {
            var node = change.Object.GetStateTreeNode() as ObjectNode;

            node.AssertWritable();

            var map = change.Object as IObservableMap <string, INode, T>;

            switch (change.Type)
            {
            case ChangeType.UPDATE:
            {
                var oldValue = map.GetValue(change.Name);

                if (change.NewValue == oldValue)
                {
                    return(null);
                }

                StateTreeUtils.Typecheck(SubType, change.NewValue.StoredValue);

                change.NewValue = SubType.Reconcile(node.GetChildNode(change.Name), change.NewValue.StoredValue);

                ProcessIdentifier(change.Name, change.NewValue);
            }
            break;

            case ChangeType.ADD:
            {
                StateTreeUtils.Typecheck(SubType, change.NewValue);

                change.NewValue = SubType.Instantiate(node, change.Name, Node.Environment, change.NewValue.StoredValue);

                ProcessIdentifier(change.Name, change.NewValue);
            }
            break;
            }

            return(change);
        }
        public static bool AreSame(INode oldNode, object newValue)
        {
            // the new value has the same node
            if (newValue.IsStateTreeNode())
            {
                return(newValue.GetStateTreeNode() == oldNode);
            }

            if (StateTreeUtils.IsMutatble(newValue) && oldNode.Snapshot == newValue)
            {
                return(true);
            }

            if (oldNode is ObjectNode oNode)
            {
                if (!string.IsNullOrWhiteSpace(oNode.Identifier) && !string.IsNullOrWhiteSpace(oNode.IdentifierAttribute))
                {
                    return(EqualityComparer <object> .Default.Equals(StateTreeUtils.GetPropertyValue(newValue, oNode.IdentifierAttribute), oNode.Identifier));
                }
            }

            return(false);
        }
Beispiel #13
0
        public ObjectNode(IType type,
                          ObjectNode parent, string subpath,
                          IEnvironment environment,
                          object initialSnapshot,
                          Func <object, IStateTreeNode, object> createNewInstance,
                          Action <INode, object, IStateTreeNode> finalizeNewInstance = null)
        {
            NodeId = ++NextNodeId;

            Type = type;

            //_initialSnapshot = initialSnapshot;
            //_createNewInstance = createNewInstance;
            //_finalizeNewInstance = finalizeNewInstance;

            State = NodeLifeCycle.INITIALIZING;

            SubpathAtom = new Atom("path");

            Subpath        = subpath;
            EscapedSubpath = subpath.EscapeJsonPath();

            Parent = parent;

            Environment = environment;

            _IsRunningAction = false;

            IsProtectionEnabled = true;

            AutoUnbox = true;

            PreBoot();

            if (type is IObjectType objectType)
            {
                IdentifierAttribute = objectType.IdentifierAttribute;

                // identifier can not be changed during lifecycle of a node
                // so we safely can read it from initial snapshot

                if (!string.IsNullOrWhiteSpace(IdentifierAttribute))
                {
                    Identifier = Convert.ToString(StateTreeUtils.GetPropertyValue(initialSnapshot, IdentifierAttribute));
                }
            }

            if (parent == null)
            {
                IdentifierCache = new IdentifierCache();
            }

            var childNodes = type.InitializeChildNodes(this, initialSnapshot);

            if (parent == null)
            {
                IdentifierCache.AddNodeToCache(this);
            }
            else
            {
                parent.Root.IdentifierCache.AddNodeToCache(this);
            }

            IStateTreeNode meta = new StateTreeNode(this);

            StoredValue = createNewInstance(childNodes, meta);

            PostBoot();

            PreCompute();

            bool sawException = true;

            try
            {
                _IsRunningAction = true;

                finalizeNewInstance?.Invoke(this, childNodes, meta);

                _IsRunningAction = false;

                FireHook(Hook.AfterCreate);

                State = NodeLifeCycle.CREATED;

                sawException = false;
            }
            finally
            {
                if (sawException)
                {
                    // short-cut to die the instance, to avoid the snapshot computed starting to throw...
                    State = NodeLifeCycle.DEAD;
                }
            }

            // NOTE: we need to touch snapshot, because non-observable
            // "observableInstanceCreated" field was touched
            // _Snapshot.TrackAndCompute();

            if (IsRoot)
            {
                AddSnapshotReaction();
            }

            FinalizeCreation();

            // _childNodes = null;
            // _initialSnapshot = null;
            // _createNewInstance = null;
            // _finalizeNewInstance = null;
        }
 public static string GetRelativePath(this object target, object relative)
 {
     return(StateTreeUtils.GetRelativePathBetweenNodes(target.GetStateTreeNode(), relative.GetStateTreeNode()));
 }
        protected override IValidationError[] IsValidSnapshot(object values, IContextEntry[] context)
        {
            if (values is IDictionary <object, object> dictionary)
            {
                var errors = dictionary.Keys.Select(key => SubType.Validate(dictionary[key], StateTreeUtils.GetContextForPath(context, $"{key}", SubType)));

                return(errors.Aggregate(new IValidationError[] { }, (acc, value) => acc.Concat(value).ToArray()));
            }

            return(new IValidationError[]
            {
                new ValidationError
                {
                    Context = context,

                    Value = values,

                    Message = $"Value is not an dictionary"
                }
            });
        }
        public static List <INode> ReconcileListItems <Sx, Tx>(ObjectNode parent, IType <Sx, Tx> type, List <INode> oldNodes, object[] newValues, string[] newPaths)
        {
            INode oldNode, oldMatch;

            object newValue;

            bool hasNewNode = false;

            for (int i = 0; ; i++)
            {
                hasNewNode = i <= newValues.Length - 1;
                oldNode    = i < oldNodes.Count ? oldNodes[i] : null;
                newValue   = hasNewNode ? newValues[i] : null;

                // for some reason, instead of newValue we got a node, fallback to the storedValue
                // TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
                if (StateTreeUtils.IsNode(newValue) || newValue is TempNode)
                {
                    newValue = (newValue as INode).StoredValue;
                }

                // both are empty, end
                if (oldNode == null && !hasNewNode)
                {
                    break;
                    // new one does not exists, old one dies
                }
                else if (!hasNewNode)
                {
                    oldNode.Dispose();
                    oldNodes.Splice(i, 1);
                    i--;
                    // there is no old node, create it
                }
                else if (oldNode == null)
                {
                    // check if already belongs to the same parent. if so, avoid pushing item in. only swapping can occur.
                    if (newValue.IsStateTreeNode() && newValue.GetStateTreeNode().Parent == parent)
                    {
                        // this node is owned by this parent, but not in the reconcilable set, so it must be double
                        throw new Exception($"Cannot add an object to a state tree if it is already part of the same or another state tree.Tried to assign an object to '{parent.Path}/{newPaths[i]}', but it lives already at '{newValue.GetStateTreeNode().Path}'");
                    }
                    oldNodes.Splice(i, 0, ValueAsNode(type, parent, newPaths[i], newValue));
                }
                else if (AreSame(oldNode, newValue)) // both are the same, reconcile
                {
                    oldNodes[i] = ValueAsNode(type, parent, newPaths[i], newValue, oldNode);
                    // nothing to do, try to reorder
                }
                else
                {
                    oldMatch = null;

                    // find a possible candidate to reuse
                    for (int j = i; j < oldNodes.Count; j++)
                    {
                        if (AreSame(oldNodes[j], newValue))
                        {
                            oldMatch = oldNodes.Splice(j, 1)[0];
                            break;
                        }
                    }

                    oldNodes.Splice(i, 0, ValueAsNode(type, parent, newPaths[i], newValue, oldMatch));
                }
            }

            return(oldNodes);
        }