using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using miew.Enumerable; using miew.Memory; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// IHotValue(T) : a push-forward computation mesh /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace miew.Mesh { using Math = System.Math; using String = System.String; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public interface IHotValueListener<T> { void Recalculate(); /// <summary> /// This convenience method should be implemented like so: /// public void ListenTo(IHotValue(T) bv) { bv.AddListener(this); } /// </summary> void ListenTo(IHotValue<T> bv); /// <summary> /// This convenience method should be implemented like so: /// public void StopListeningTo(IHotValue<T> bv) { bv.RemoveListener(this); } /// </summary> void StopListeningTo(IHotValue<T> bv); }; public interface IHotValue<T> { void AddListener(IHotValueListener<T> bv); void RemoveListener(IHotValueListener<T> bv); T Value { get; } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// An IHotValue(T) instance which supports a value of T that never changes. /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public class HotValueConst<T> : IHotValue<T> { public static HotValueConst<T> Default = new HotValueConst<T>(default(T)); #if DEBUG readonly #endif T val; public HotValueConst(T value) { this.val = value; } public T Value { get { return val; } } /// Never have to notify, so there is no point in keeping track of listeners public void AddListener(IHotValueListener<T> bv) { } public void RemoveListener(IHotValueListener<T> bv) { } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Abstract implementation helper for an IHotValue(T) which needs to notify subscribers of changes to its /// value because that value is subject to change (i.e. perhaps because the IHotValue(T) is itself a subscriber /// to other HotValues although this is not a specific requirement here) /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public abstract class HotValueNotifier<T> : IHotValue<T> where T : IEquatable<T> { List<WeakReference<IHotValueListener<T>>> listeners = null; public void AddListener(IHotValueListener<T> hv) { if (hv == null) return; listeners = listeners ?? new List<WeakReference<IHotValueListener<T>>>(); listeners.Add(new WeakReference<IHotValueListener<T>>(hv)); } public void RemoveListener(IHotValueListener<T> hv) { if (listeners == null || hv == null) return; for (int i = 0; i < listeners.Count; i++) { if (hv == listeners[i].Target) { listeners.RemoveAt(i); return; } } } public void Notify() { if (listeners == null) return; for (int i = 0; i < listeners.Count; ) { IHotValueListener<T> bv = listeners[i].Target; if (bv == null) listeners.RemoveAt(i); else { bv.Recalculate(); i++; } } } public abstract T Value { get; } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Implementation of an IHotValue(T) that can (only) be explicitly changed. Such manual changes trigger notifications /// to this object's subscribers. /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public class HotValueStatic<T> : HotValueNotifier<T> where T : IEquatable<T> { T val; public HotValueStatic(T initial_value) { this.val = initial_value; } public override T Value { get { return val; } } public void SetValue(T value) { if (!value.Equals(this.val)) { this.val = value; Notify(); } } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Implementation of an IHotValue(T) repeater which allows the same notification targets to be maintained when the source /// IHotValueTarget(T) is replaced. To change the source, call the 'ChangeSource' method. As an optimization, the last /// reported value of the source is cached and if the value of the new source is equal the previous value, notifications /// are not sent. /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public class HotValueDynamic<T> : HotValueNotifier<T>, IHotValueListener<T> where T : IEquatable<T> { public HotValueDynamic(IHotValue<T> source) { this.src = source; this.val = src == null ? default(T) : src.Value; ListenTo(src); } IHotValue<T> src; T val; public override T Value { get { return src.Value; } } public void Recalculate() { T new_val = src == null ? default(T) : src.Value; if (!new_val.Equals(val)) { val = new_val; Notify(); } } public void ListenTo(IHotValue<T> hv) { hv.AddListener(this); } public void StopListeningTo(IHotValue<T> hv) { hv.RemoveListener(this); } public IHotValue<T> Source { get { return src; } set { StopListeningTo(src); src = value; ListenTo(src); Recalculate(); } } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Abstract implementation helper for a HotValueCalc(S,T) instance: /// (a.) maintains a single cached value of type 'T' which is automatically computed based on zero or more IHotValue(S) /// input values, and /// (b.) itself implements IHotValue(T) allowing it to be used in an heterotypical automatic calculation mesh. /// Derived classes must override the 'Calculate' function to implement their cutomized calculation. /// </summary> /// <remarks> /// Changes to any of the input values automatically trigger recalculation and notification of this object's subscribers. /// The number of IHotValue(S) input sources is set upon construction and cannot be modified, but these arguments can be /// replaced by calling 'ChangeArg' /// </remarks> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public abstract class HotValueCalc<S, T> : HotValueNotifier<T>, IHotValueListener<S> where T : IEquatable<T> { T val; readonly IHotValue<S>[] args; static bool f_double; static HotValueCalc() { f_double = default(T).GetType() == typeof(Double); } public HotValueCalc(params IHotValue<S>[] args) : this(true, args) { } public HotValueCalc(bool f_calc, params IHotValue<S>[] args) { this.args = args; foreach (IHotValue<S> bv in args) ListenTo(bv); /// calculate and store the initial value, bypassing the check for vacuous value setting. /// also skip broadcasting since there can be no subscribers initially if (f_calc) this.val = Calculate(); } public void ListenTo(IHotValue<S> bv) { bv.AddListener(this); } public void StopListeningTo(IHotValue<S> bv) { bv.RemoveListener(this); } public override T Value { get { return val; } } public void Recalculate() { T value = Calculate(); //Debug.WriteLine(value); //Debug.WriteLine(); // if (new StackTrace().FrameCount > 200) // Debugger.Break(); //Debug.WriteLine(new StackTrace().FrameCount); if (f_double) { if (Math.Abs(Convert.ToDouble(this.val) - Convert.ToDouble(value)) < .00001) return; } if (!value.Equals(this.val)) { this.val = value; Notify(); } } protected IHotValue<S>[] Args { get { return args; } } protected S Arg0 { get { return args[0] == null ? default(S) : args[0].Value; } } protected S Arg1 { get { return args[1] == null ? default(S) : args[1].Value; } } protected S Arg2 { get { return args[2] == null ? default(S) : args[2].Value; } } protected S Arg3 { get { return args[3] == null ? default(S) : args[3].Value; } } protected S Arg4 { get { return args[4] == null ? default(S) : args[4].Value; } } protected S Arg5 { get { return args[5] == null ? default(S) : args[5].Value; } } public void ChangeArg(IHotValue<S> old_arg, IHotValue<S> new_arg) { if (old_arg == null || new_arg == null) throw new ArgumentException("Cannot be null."); int ix = System.Array.IndexOf<IHotValue<S>>(args, old_arg); if (ix == -1) throw new ArgumentException("The argument to replace was not found."); if (old_arg == new_arg) return; do { StopListeningTo(old_arg); args[ix] = new_arg; ListenTo(new_arg); } while ((ix = System.Array.IndexOf<IHotValue<S>>(args, old_arg, ix)) != -1); Recalculate(); } protected abstract T Calculate(); }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Abstract implementation helper for calculated IHotValue(T) values where the input type is the same as the type /// of the calculation output /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public abstract class HotValueCalc<T> : HotValueCalc<T, T> where T : IEquatable<T> { public HotValueCalc(params IHotValue<T>[] args) : base(true, args) { } public HotValueCalc(bool f_calc, params IHotValue<T>[] args) : base(f_calc, args) { } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// IHvDouble - supporting a mesh of double-precision calculations /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public interface IHvDouble : IHotValue<Double> { } [DebuggerDisplay("{ToString(),nq}")] public class HvdConst : HotValueConst<Double>, IHvDouble { public HvdConst(Double value) : base(value) { } public override string ToString() { return String.Format("{0:X8} Constant = {1}", GetHashCode(), Value); } public static HvdConst NegativeInfinity = new HvdConst(double.NegativeInfinity); public static HvdConst PositiveInfinity = new HvdConst(double.PositiveInfinity); public static HvdConst Zero = new HvdConst(0); public static HvdConst NaN = new HvdConst(double.NaN); }; [DebuggerDisplay("{ToString(),nq}")] public class HvdStatic : HotValueStatic<Double>, IHvDouble { public HvdStatic(Double value) : base(value) { } public override string ToString() { return String.Format("{0:X8} Static = {1}", GetHashCode(), Value); } }; [DebuggerDisplay("{ToString(),nq}")] public class HvdDynamic : HotValueDynamic<Double>, IHvDouble { public HvdDynamic(IHvDouble bv_arg) : base(bv_arg) { } public override string ToString() { return String.Format("{0:X8} Dynamic = {1}", GetHashCode(), Value); } }; [DebuggerDisplay("{ToString(),nq}")] public class HvdAdd : HotValueCalc<Double>, IHvDouble { public HvdAdd(params IHvDouble[] args) : base(args) { } protected override Double Calculate() { return Args.Sum(x => x.Value); } public override string ToString() { return String.Format("{0:X8} {1} + {2} = {3}", GetHashCode(), Arg0, Arg1, Value); } }; [DebuggerDisplay("{ToString(),nq}")] public class HvdAddConst : HotValueCalc<Double>, IHvDouble { public HvdAddConst(Double dVal, params IHvDouble[] args) : base(false, args) { this.dVal = dVal; Recalculate(); } Double dVal; protected override Double Calculate() { return dVal + Args.Sum(x => x.Value); } public override string ToString() { return String.Format("{0:X8} /{1}/ + {2} = {3}", GetHashCode(), dVal, Args.Select(x => x.Value.ToString()).StringJoin(" + "), Value); } }; [DebuggerDisplay("{ToString(),nq}")] public class HvdMax : HotValueCalc<Double>, IHvDouble { public HvdMax(IHvDouble bv0, IHvDouble bv1) : base(bv0, bv1) { } protected override Double Calculate() { return Math.Max(Arg0, Arg1); } public override string ToString() { return String.Format("{0:X8} Max({1},{2}) = {3}", GetHashCode(), Arg0, Arg1, Value); } }; public class HvdMultiMax : HotValueCalc<Double>, IHvDouble { public HvdMultiMax(params IHvDouble[] args) : base(args) { } protected override Double Calculate() { return Args.Max(x => x.Value); } }; [DebuggerDisplay("{ToString(),nq}")] public class HvdMin : HotValueCalc<Double>, IHvDouble { public HvdMin(IHvDouble bv0, IHvDouble bv1) : base(bv0, bv1) { } protected override Double Calculate() { return Math.Min(Arg0, Arg1); } public override string ToString() { return String.Format("{0:X8} Min({1},{2}) = {3}", GetHashCode(), Arg0, Arg1, Value); } }; public class HvdMultiMin : HotValueCalc<Double>, IHvDouble { public HvdMultiMin(params IHvDouble[] args) : base(args) { } protected override Double Calculate() { return Args.Min(x => x.Value); } }; public class HvdAverage : HotValueCalc<Double>, IHvDouble { public HvdAverage(params IHvDouble[] args) : base(args) { } protected override Double Calculate() { return Args.Average(x => x.Value); } }; public class HvdAddHalf : HotValueCalc<Double>, IHvDouble { public HvdAddHalf(IHvDouble bv0, IHvDouble bv1) : base(bv0, bv1) { } protected override Double Calculate() { return Arg0 + (Arg1 / 2); } }; public class HvdSubHalf : HotValueCalc<Double>, IHvDouble { public HvdSubHalf(IHvDouble bv0, IHvDouble bv1) : base(bv0, bv1) { } protected override Double Calculate() { return Arg0 - (Arg1 / 2); } }; public class HvdCenterNode : HotValueCalc<Double>, IHvDouble { public HvdCenterNode(IHvDouble bv0, IHvDouble bv1, IHvDouble width) : base(bv0, bv1, width) { } protected override Double Calculate() { return Math.Abs(Arg1 - Arg0) / 2 - Arg2 / 2; } }; public class HvdHalfSpan : HotValueCalc<Double>, IHvDouble { public HvdHalfSpan(IHvDouble bv0, IHvDouble bv1) : base(bv0, bv1) { } protected override Double Calculate() { return Math.Abs(Arg1 - Arg0) / 2; } }; public class HvdAddHalfSpan : HotValueCalc<Double>, IHvDouble { public HvdAddHalfSpan(IHvDouble bv0, IHvDouble bv1, IHvDouble bv2) : base(bv0, bv1, bv2) { } protected override Double Calculate() { return Arg0 + (Arg2 - Arg1) / 2; } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// IHvInterval - supporting a mesh of calculations involving intervals /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public interface IHvInterval : IHotValue<IHvInterval> { IHvDouble HvdX1 { get; } IHvDouble HvdX2 { get; } IHvDouble HvdLength { get; } IHvDouble HvdCenter { get; } Double X1 { get; } Double X2 { get; } Double Length { get; } Double Center { get; } }; public class HvIntervalBuilder : IHotValueListener<Double>, IHvInterval, IHotValueListener<IHvInterval> { public HvIntervalBuilder() { x1 = new HvdDynamic(null); x2 = new HvdDynamic(null); x1.AddListener(this); x2.AddListener(this); } List<WeakReference<IHotValueListener<IHvInterval>>> listeners = null; HvdDynamic x1 = null; HvdDynamic x2 = null; HvdStatic length = null; HvdStatic center = null; public IHvDouble HvdX1 { get { return x1; } } public IHvDouble HvdX2 { get { return x2; } } public IHvDouble HvdLength { get { return length = length ?? new HvdStatic(Length); } } public IHvDouble HvdCenter { get { return center = center ?? new HvdStatic(Center); } } public Double X1 { get { return x1.Value; } } public Double X2 { get { return x2.Value; } } public Double Length { get { return x2.Value - x1.Value; } } public Double Center { get { return x1.Value + (x2.Value - x1.Value) / 2.0; } } void IHotValueListener<Double>.Recalculate() { Notify(); } void IHotValueListener<IHvInterval>.Recalculate() { Notify(); } void IHotValueListener<Double>.ListenTo(IHotValue<Double> bv) { bv.AddListener(this); } void IHotValueListener<IHvInterval>.ListenTo(IHotValue<IHvInterval> bv) { bv.AddListener(this); } void IHotValueListener<Double>.StopListeningTo(IHotValue<Double> bv) { bv.RemoveListener(this); } void IHotValueListener<IHvInterval>.StopListeningTo(IHotValue<IHvInterval> bv) { bv.AddListener(this); } public void AddListener(IHotValueListener<IHvInterval> hv) { if (hv == null) return; listeners = listeners ?? new List<WeakReference<IHotValueListener<IHvInterval>>>(); listeners.Add(new WeakReference<IHotValueListener<IHvInterval>>(hv)); } public void RemoveListener(IHotValueListener<IHvInterval> hv) { if (listeners == null || hv == null) return; for (int i = 0; i < listeners.Count; i++) { if (hv == listeners[i].Target) { listeners.RemoveAt(i); return; } } } public void Notify() { if (listeners == null) return; for (int i = 0; i < listeners.Count; ) { IHotValueListener<IHvInterval> bv = listeners[i].Target; if (bv == null) listeners.RemoveAt(i); else { bv.Recalculate(); i++; } } } public IHvInterval Value { get { return this; } } }; }