From 939395eea95eb44345e92959773c6289c55fdbfe Mon Sep 17 00:00:00 2001 From: Daniil Korostelev Date: Fri, 21 Jun 2024 18:07:39 +0200 Subject: [PATCH] wip --- playground/Playground.cs | 62 +++++++++---- .../Runtime/ExternalObservableSource.cs | 11 +++ .../Internal/ExternalObservableSource.cs | 91 +++++++++++++++++++ src/TinkState/Runtime/Observable.cs | 7 +- 4 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 src/TinkState/Runtime/ExternalObservableSource.cs create mode 100644 src/TinkState/Runtime/Internal/ExternalObservableSource.cs diff --git a/playground/Playground.cs b/playground/Playground.cs index 497a760..ac9b441 100644 --- a/playground/Playground.cs +++ b/playground/Playground.cs @@ -1,35 +1,61 @@ using System; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; +using System.Timers; using TinkState; +using Timer = System.Timers.Timer; class Playground { - static async Task Main() + static void Main() { - var stateA = Observable.State("hello"); - var stateB = Observable.State("world"); + var time = CreateTimeObservable(); + var o = Observable.Auto(() => (time.Value, time.Value.ToShortTimeString())); + // var o = time; - var o = Observable.Auto(async () => + Console.WriteLine(o.Value); + Thread.Sleep(2000); + Console.WriteLine(o.Value); + // + // var counter = 0; + // IDisposable binding = null; + // binding = o.Bind(v => + // { + // Console.WriteLine($"Current time is: {v}"); + // counter++; + // if (counter >= 5) binding.Dispose(); + // }); + + Process.GetCurrentProcess().WaitForExit(); + } + + private static Observable CreateTimeObservable() + { + var time = Observable.External(() => DateTime.UtcNow); + + var timer = new Timer(1000); + + var timerElapsedHandler = new ElapsedEventHandler((_, _) => { - Console.WriteLine("computing"); - var a = stateA.Value; - await Task.Delay(1000); - var b = stateB.Value; - return a + " " + b; + Console.WriteLine("tick"); + time.Invalidate(); }); - o.Bind(result => Console.WriteLine(result.Status switch + time.Subscribed += () => { - AsyncComputeStatus.Loading => "Loading...", - AsyncComputeStatus.Done => "Done: " + result.Result, - AsyncComputeStatus.Failed => "Failed: " + result.Exception, - })); - - await Task.Delay(1500); + Console.WriteLine("wakeup"); + timer.Elapsed += timerElapsedHandler; + timer.Start(); + }; - stateB.Value = "Dan"; + time.Unsubscribed += () => + { + Console.WriteLine("sleep"); + timer.Elapsed -= timerElapsedHandler; + timer.Stop(); + }; - Process.GetCurrentProcess().WaitForExit(); + return time; } } diff --git a/src/TinkState/Runtime/ExternalObservableSource.cs b/src/TinkState/Runtime/ExternalObservableSource.cs new file mode 100644 index 0000000..d7cce3c --- /dev/null +++ b/src/TinkState/Runtime/ExternalObservableSource.cs @@ -0,0 +1,11 @@ +using System; + +namespace TinkState +{ + public interface ExternalObservableSource : Observable + { + event Action Subscribed; + event Action Unsubscribed; + void Invalidate(); + } +} diff --git a/src/TinkState/Runtime/Internal/ExternalObservableSource.cs b/src/TinkState/Runtime/Internal/ExternalObservableSource.cs new file mode 100644 index 0000000..61f0511 --- /dev/null +++ b/src/TinkState/Runtime/Internal/ExternalObservableSource.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace TinkState.Internal +{ + class ExternalObservableSource : Dispatcher, TinkState.ExternalObservableSource, DispatchingObservable + { + public event Action Subscribed; + public event Action Unsubscribed; + + readonly Func getter; + readonly IEqualityComparer comparer; + + bool isSubscribedTo; + bool valid; + T last; + + public ExternalObservableSource(Func getter, IEqualityComparer comparer) + { + this.getter = getter; + this.comparer = comparer ?? EqualityComparer.Default; + } + + public IDisposable Bind(Action callback, IEqualityComparer comparer = null, Scheduler scheduler = null) + { + return new Binding(this, callback, comparer, scheduler); + } + + public Observable Map(Func transform, IEqualityComparer comparer = null) + { + return new TransformObservable(this, transform, comparer); + } + + public IEqualityComparer GetComparer() + { + return comparer; + } + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public T Value => AutoObservable.Track(this); + + public void Invalidate() + { + if (valid) + { + valid = false; + Fire(); + } + } + + public T GetCurrentValue() + { + if (!valid || !isSubscribedTo) + { + Calculate(); + } + return last; + } + + public long GetRevision() + { + // TODO: auto-observables rely on this so what should we do? + return revision; + } + + void Calculate() + { + last = getter(); + valid = true; + } + + protected override void OnStatusChange(bool active) + { + if (active) WakeUp(); else Sleep(); + } + + void WakeUp() + { + isSubscribedTo = true; + Calculate(); + Subscribed?.Invoke(); + } + + void Sleep() + { + isSubscribedTo = false; + Unsubscribed?.Invoke(); + } + } +} diff --git a/src/TinkState/Runtime/Observable.cs b/src/TinkState/Runtime/Observable.cs index 36fb641..b4c46b0 100644 --- a/src/TinkState/Runtime/Observable.cs +++ b/src/TinkState/Runtime/Observable.cs @@ -179,6 +179,11 @@ public static State State(T initialValue, IEqualityComparer comparer = return new Internal.State(initialValue, comparer); } + public static ExternalObservableSource External(Func getter, IEqualityComparer comparer = null) + { + return new Internal.ExternalObservableSource(getter, comparer); + } + /// /// Create an empty observable list. /// @@ -220,4 +225,4 @@ public static ObservableDictionary Dictionary() /// public static Scheduler Scheduler = Scheduler.Direct; } -} \ No newline at end of file +}