using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text; using System.Threading.Tasks; using DynamicData; using DynamicData.Alias; using DynamicData.Kernel; using NodeNetwork.ViewModels; using NodeNetwork.Views; using ReactiveUI; namespace NodeNetwork.Toolkit.ValueNode { /// /// A node input that keeps a list of the latest values produced by all of the connected ValueNodeOutputViewModels. /// This input can take multiple connections, ValueNodeInputViewModel cannot. /// /// The type of object this input can receive public class ValueListNodeInputViewModel : NodeInputViewModel { static ValueListNodeInputViewModel() { NNViewRegistrar.AddRegistration(() => new NodeInputView(), typeof(IViewFor>)); } /// /// The current values of the outputs connected to this input /// public IObservableList Values { get; } public ValueListNodeInputViewModel() { MaxConnections = Int32.MaxValue; ConnectionValidator = pending => new ConnectionValidationResult( pending.Output is ValueNodeOutputViewModel || pending.Output is ValueNodeOutputViewModel>, null ); var valuesFromSingles = Connections.Connect(c => c.Output is ValueNodeOutputViewModel) .Transform(c => (ValueNodeOutputViewModel)c.Output) //Note: this line used to be //.AutoRefresh(output => output.CurrentValue) //which ignored changes where CurrentValue didn't change. //This caused problems when the value object isn't replaced, but one of its properties changes. .AutoRefreshOnObservable(output => output.Value) // Null values are not allowed, so filter before transform .Filter(output => output.CurrentValue != null) .Transform(output => output.CurrentValue, true) // Any 'replace' changes that don't change the value should be refresh changes // This prevents issues where a value is updated, but it doesn't propagate through the network // because the connections didn't change. .Select(changes => { if (changes.TotalChanges == changes.Replaced + changes.Refreshes) { bool allRefresh = true; var newChanges = new ChangeSet(); foreach (var change in changes) { if (change.Reason == ListChangeReason.Replace) { if (change.Type == ChangeType.Item) { if (change.Item.Previous != change.Item.Current) { allRefresh = false; break; } newChanges.Add(new Change(ListChangeReason.Refresh, change.Item.Current, change.Item.Previous, change.Item.CurrentIndex, change.Item.PreviousIndex)); } else { throw new Exception("Does this ever occur?"); } } else { newChanges.Add(change); } } if (allRefresh) return newChanges; } return changes; }); var valuesFromLists = Connections.Connect(c => c.Output is ValueNodeOutputViewModel>) // Grab list of values from output, using switch to handle when the list object is replaced .Transform(c => ((ValueNodeOutputViewModel>) c.Output).Value.Switch()) // Materialize this changeset stream into a list (needed to make sure the next step is done dynamically) .AsObservableList() // Take the union of all values from all lists. This is done dynamically, so adding/removing new lists works as expected. .Or(); Values = valuesFromSingles.Or(valuesFromLists).AsObservableList(); } } }