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" } }); }
private S GetDefaultValue() { var defaultValue = _DefaultValue != null?_DefaultValue() : default(S); StateTreeUtils.Typecheck(_Type, defaultValue); return(defaultValue); }
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)); }
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); }
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); }