using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Chernobyl.Event; namespace Chernobyl.Values { /// /// An that represents multiple /// instances. This type will only raise /// if all contained /// instances have provided values. Additionally, after all /// instances have provided values, this type will /// raise when any of the contained /// instances raise there . /// /// The type that is contained with the /// this type will be composed of. public class Composite : BasicValue> { /// /// Initializes a new instance of the class. /// /// The instances whose internal value is to be /// represented as an by this instance. public Composite(IEnumerable> values) { values.ThrowIfNull("values"); _values = values.ToArray(); } /// /// The value held by this instance. If the value has not yet been /// provided it will be equal to default(T). Setting this property /// will make this instance ready. /// protected override IEnumerable BackingValue { get { return _value; } set { _value.Clear(); _value.AddRange(value); } } /// /// The previous value of . /// protected override IEnumerable PreviousValue { get { return _previousValue; } set { _previousValue.Clear(); _previousValue.AddRange(value); } } /// /// Invoked when is changed to true. /// Implementations of this method should invoke the base class method. /// protected override void Needed() { // Set the capacity so that we don't have to trim the excess after // we've added the defaults values. _value.Capacity = _previousValue.Capacity = _valuesProvided.Length = _eventHandlers.Capacity = _values.Length; // Now ensure that we get the values and their updates. if (_values.Length != 0) { // Add default values so that we can just set the values when they // are provided. T[] defaults = new T[_values.Length]; _value.AddRange(defaults); _previousValue.AddRange(defaults); _eventHandlers.AddRange(new EventHandler>[_values.Length]); for (int i = 0; i < _values.Length; i++) { int index = i; // Variable to prevent modified closure issue. // Create, store, and assign the event handler. EventHandler> eventHandler = (sender, e) => OnValueProvide(e, index); _eventHandlers[index] = eventHandler; _values[i].Provide += eventHandler; } } else IsReady = true; base.Needed(); } /// /// Invoked when is changed to false. /// Implementations of this method should invoke the base class method. /// protected override void NotNeeded() { for (int i = 0; i < _values.Length; i++) _values[i].Provide -= _eventHandlers[i]; _previousValue.Clear(); _value.Clear(); _eventHandlers.Clear(); _valuesProvided.SetAll(false); _providedCount = 0; IsReady = false; base.NotNeeded(); } /// /// Invoked when one of the contained by this /// instance has it value changed or made ready. /// /// The /// instance containing the event data. /// The index of the item that was changed. void OnValueProvide(ValueChangedEventArgs e, int index) { _previousValue[index] = e.OldValue; _value[index] = e.NewValue; // Check if this Composite can be made ready. If we are ready then // we can notify client code of the change. bool raiseProvide = IsReady; if (_valuesProvided[index] == false) { _valuesProvided[index] = true; _providedCount++; // If all of the values have been provided, then we are ready. if (_providedCount == _values.Length) { // Don't invoke Provide if we change IsReady to true since // IsReady will invoke Provide for us. if (IsReady == false) IsReady = true; else raiseProvide = true; } } if (raiseProvide && ProvideHandler != null) ProvideHandler(this, new ValueChangedEventArgs>( PreviousValue, Value)); } /// /// The instances whose internal value is to be represented as an /// by this instance. /// readonly IValue[] _values; /// /// Specifies which values have been provided where 0 is not ready, 1 is /// ready. /// readonly BitArray _valuesProvided = new BitArray(0); /// /// The number of instances in that have provided /// a first value. /// uint _providedCount; /// /// The instance that contains the event handlers that react to the /// providing of values by the instance contained in . /// readonly List>> _eventHandlers = new List>>(); /// /// The backing field to . /// readonly List _value = new List(); /// /// The backing field to . /// readonly List _previousValue = new List(); } /// /// Extension and utility methods for . /// public static class CompositeExt { /// /// Returns an that represents multiple /// instances. The instance will only raise if all contained /// instances have provided values. Additionally, after all /// instances have provided values, this type will raise /// when any of the contained /// instances raises its event. /// public static IValue> Composite(this IEnumerable> values) => new Composite(values); } }