From 4da62618320671eedd6ba3e41a034af87fb39e4b Mon Sep 17 00:00:00 2001 From: Bernhard Straub Date: Sun, 25 Feb 2024 18:41:53 +0100 Subject: [PATCH] RX State improve Observable and simplify service states Simplify observable states and remove dedicated service state providers --- RxBlazorLightCore/Core/Interfaces.cs | 22 ++- RxBlazorLightCore/Core/RxExtensions.cs | 16 +- RxBlazorLightCore/Core/State.cs | 173 +++++++----------- .../ServiceFixture.State.cs | 9 +- RxBlazorLightCoreTestBase/ServiceFixture.cs | 17 +- RxBlazorLightCoreTests/StateTests.cs | 72 ++++++-- RxMudBlazorLight/RxMudBlazorLight.csproj | 2 +- .../Components/TestPlayground.razor | 6 +- .../TestService.ProvidersTransformers.cs | 12 +- .../Service/TestService.cs | 8 +- 10 files changed, 170 insertions(+), 167 deletions(-) diff --git a/RxBlazorLightCore/Core/Interfaces.cs b/RxBlazorLightCore/Core/Interfaces.cs index a450f3e..6378f8b 100644 --- a/RxBlazorLightCore/Core/Interfaces.cs +++ b/RxBlazorLightCore/Core/Interfaces.cs @@ -41,9 +41,9 @@ public interface IState : IStateTransformer where TTyp public interface IState : IState; - public interface IState : IState + public interface IState : IState { - public static object? Default { get; } = default; + public static Unit Default { get; } = Unit.Default; } public interface IStateGroup : IState @@ -64,6 +64,7 @@ public enum StateChangePhase NONE, CHANGING, CHANGED, + COMPLETED, CANCELED, EXCEPTION } @@ -84,23 +85,24 @@ public interface IStateTransformer : IStateProvideTransformBase public bool CanTransform(T? value); } - public interface IStateProvider : IStateProvideTransformBase + public interface IObservableStateProvider : IStateProvideTransformBase, IObserver { - public void Provide(); - - public bool CanProvide(T? value); + void Provide(T value); } - public interface IServiceStateTransformer : IStateTransformer + public interface IObservableStateProvider : IObservableStateProvider { + void Provide(); } - public interface IServiceStateProvider : IStateProvider + public interface IStateProvider : IStateProvideTransformBase { + public void Provide(); + + public bool CanProvide(T? value); } - public interface IServiceStateObserver : IObserver, IStateProvideTransformBase + public interface IStateProvider : IStateProvider { - public void Provide(); } } diff --git a/RxBlazorLightCore/Core/RxExtensions.cs b/RxBlazorLightCore/Core/RxExtensions.cs index fa9bafb..5babd42 100644 --- a/RxBlazorLightCore/Core/RxExtensions.cs +++ b/RxBlazorLightCore/Core/RxExtensions.cs @@ -1,4 +1,6 @@  +using System.Reactive; + namespace RxBlazorLightCore { public static class RxExtensions @@ -15,9 +17,14 @@ public static IState CreateState(this S service, TType? value, return State.Create(service, value, valueProviderFactory); } - public static IServiceStateObserver CreateStateObserver(this S service) where S : RxBLService + public static IObservableStateProvider CreateObservableStateProvider(this S service) where S : RxBLService + { + return ObservableStateProvider.Create(service); + } + + public static IObservableStateProvider CreateObservableStateProvider(this S service, IState state) where S : RxBLService { - return new ServiceStateObserver(service); + return ObservableStateProvider.Create(service, state); } public static bool Changing(this IStateProvideTransformBase valueProvider) @@ -30,6 +37,11 @@ public static bool Changed(this IStateProvideTransformBase valueProvider) return valueProvider.Phase is StateChangePhase.CHANGED; } + public static bool Completed(this IStateProvideTransformBase valueProvider) + { + return valueProvider.Phase is StateChangePhase.COMPLETED; + } + public static bool Canceled(this IStateProvideTransformBase valueProvider) { return valueProvider.Phase is StateChangePhase.CANCELED; diff --git a/RxBlazorLightCore/Core/State.cs b/RxBlazorLightCore/Core/State.cs index ffb8ae6..65b97f3 100644 --- a/RxBlazorLightCore/Core/State.cs +++ b/RxBlazorLightCore/Core/State.cs @@ -1,8 +1,6 @@  -using System.Numerics; using System.Reactive; using System.Reactive.Linq; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace RxBlazorLightCore { @@ -79,7 +77,7 @@ public static IState Create(S service, TType? value, Func, } } - public class State : State, IState + public class State : State, IState where S : RxBLService { private State(S service) : base(service, IState.Default, null) { } @@ -164,7 +162,7 @@ public class StateProvideTransformBase : IStateProvideT protected internal StateProvideTransformBase(S service, IState? state, bool setState, bool runAsync) { Service = service; - State = state is null ? (State)State.Create(service, IState.Default) : (State)state; + State = state is null ? (State)State.Create(service, IState.Default) : (State)state; _setState = setState; _runAsync = runAsync; _canceled = false; @@ -326,6 +324,57 @@ public void Transform(T value) protected abstract TType? TransformState(T value); } + public class ObservableStateProvider(S service, IState? state) : + StateProvideTransformBase(service, state, true, false), IObservableStateProvider + where S : RxBLService + { + public void OnCompleted() + { + StateChanged(StateChangePhase.COMPLETED); + } + + public void OnError(Exception error) + { + StateChanged(StateChangePhase.EXCEPTION, error); + } + + public void OnNext(T value) + { + TransformBaseSync(value); + } + + public void Provide(T value) + { + OnNext(value); + } + + public static IObservableStateProvider Create(S service) + { + var state = State.Create(service, Unit.Default); + return new ObservableStateProvider(service, state); + } + + public static IObservableStateProvider Create(S service, IState state) + { + return new ObservableStateProvider(service, state); + } + } + + public class ObservableStateProvider(S service) : ObservableStateProvider(service, null), + IObservableStateProvider + where S : RxBLService + { + public void Provide() + { + Provide(Unit.Default); + } + + public static new IObservableStateProvider Create(S service) + { + return new ObservableStateProvider(service); + } + } + public abstract class StateProviderAsync(S service, IState state) : StateProvideTransformBase(service, state, true, true), IStateProvider where S : RxBLService @@ -429,66 +478,11 @@ public void Transform(T value) protected abstract void TransformState(T value, TType stateRef); } - public abstract class ServiceStateTransformerAsync(S service) : - StateProvideTransformBase(service, null, true, true), IServiceStateTransformer - where T : notnull - where S : RxBLService - { - public virtual bool CanTransform(T? _) - { - return true; - } - - public void Transform(T value) - { - TransformBaseAsync(value); - } - - protected override IObservable ProvideObervableValueBase(T? value, object? state) - { - ArgumentNullException.ThrowIfNull(value); - - return Observable.FromAsync(async cto => - { - await TransformStateAsync(value, cto); - return IState.Default; - }); - } - - protected abstract Task TransformStateAsync(T value, CancellationToken cancellationToken); - } - - public abstract class ServiceStateTransformer(S service) : - StateProvideTransformBase(service, null, true, false), IServiceStateTransformer - where T : notnull - where S : RxBLService - { - public virtual bool CanTransform(T? _) - { - return true; - } - - public void Transform(T value) - { - try - { - TransformState(value); - TransformBaseSync(IState.Default); - } - catch (Exception ex) - { - StateChanged(StateChangePhase.EXCEPTION, ex); - } - } - - protected abstract void TransformState(T value); - } - - public abstract class ServiceStateProviderAsync(S service) : - StateProvideTransformBase(service, null, true, true), IServiceStateProvider + public abstract class StateProviderAsync(S service) : + StateProvideTransformBase(service, null, true, true), IStateProvider where S : RxBLService { - public bool CanProvide(object? _) + public bool CanProvide(Unit _) { return CanProvide(); } @@ -503,7 +497,7 @@ public void Provide() TransformBaseAsync(IState.Default); } - protected override IObservable ProvideObervableValueBase(object? value, object? state) + protected override IObservable ProvideObervableValueBase(Unit value, Unit state) { return Observable.FromAsync(async cto => { @@ -515,11 +509,11 @@ public void Provide() protected abstract Task ProvideStateAsync(CancellationToken cancellationToken); } - public abstract class ServiceStateProvider(S service) : - StateProvideTransformBase(service, null, false, false), IServiceStateProvider + public abstract class StateProvider(S service) : + StateProvideTransformBase(service, null, false, false), IStateProvider where S : RxBLService { - public bool CanProvide(object? _) + public bool CanProvide(Unit _) { return CanProvide(); } @@ -542,54 +536,11 @@ public void Provide() } } - protected override IObservable ProvideObervableValueBase(object? value, object? state) + protected override IObservable ProvideObervableValueBase(Unit value, Unit state) { - return Observable.Return(Unit.Default).Select(_ => - { - ProvideState(); - return IState.Default; - }); + return Observable.Return(Unit.Default); } protected abstract void ProvideState(); } - - public class ServiceStateObserver(S service) : IServiceStateObserver where S : RxBLService - { - public Guid ID { get; } = Guid.NewGuid(); - - public StateChangePhase Phase { get; private set; } = StateChangePhase.NONE; - - public bool LongRunning => false; - - public bool CanCancel => false; - - public void Cancel() - { - throw new NotImplementedException(); - } - - public void Provide() - { - OnNext(Unit.Default); - } - - public void OnCompleted() - { - Phase = StateChangePhase.CANCELED; - service.StateHasChanged(ID, ChangeReason.STATE); - } - - public void OnError(Exception error) - { - Phase = StateChangePhase.EXCEPTION; - service.StateHasChanged(ID, ChangeReason.EXCEPTION, error); - } - - public void OnNext(Unit value) - { - Phase = StateChangePhase.CHANGED; - service.StateHasChanged(ID, ChangeReason.STATE); - } - } } \ No newline at end of file diff --git a/RxBlazorLightCoreTestBase/ServiceFixture.State.cs b/RxBlazorLightCoreTestBase/ServiceFixture.State.cs index 602759d..2beb0e4 100644 --- a/RxBlazorLightCoreTestBase/ServiceFixture.State.cs +++ b/RxBlazorLightCoreTestBase/ServiceFixture.State.cs @@ -36,19 +36,18 @@ protected override async Task TransformStateAsync(int value, CancellationTo } } - public class ChangeTestSP(ServiceFixture service) : ServiceStateTransformerAsync(service) + public class ChangeTestSP(ServiceFixture service) : StateProviderAsync(service) { public override bool CanCancel => true; - protected override async Task TransformStateAsync(string? valueIn, CancellationToken cancellationToken) + protected override async Task ProvideStateAsync(CancellationToken cancellationToken) { - ArgumentNullException.ThrowIfNull(valueIn); await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken); - Service.Test = valueIn; + Service.Test = "Async"; } } - public class ChangeTestSyncSP(ServiceFixture service) : ServiceStateProvider(service) + public class ChangeTestSyncSP(ServiceFixture service) : StateProvider(service) { protected override void ProvideState() { diff --git a/RxBlazorLightCoreTestBase/ServiceFixture.cs b/RxBlazorLightCoreTestBase/ServiceFixture.cs index 3be3bcd..69561af 100644 --- a/RxBlazorLightCoreTestBase/ServiceFixture.cs +++ b/RxBlazorLightCoreTestBase/ServiceFixture.cs @@ -1,5 +1,6 @@  using RxBlazorLightCore; +using System.Reactive; namespace RxBlazorLightCoreTestBase { @@ -7,9 +8,11 @@ public record CRUDTest(string Item, Guid Id); public partial class ServiceFixture : RxBLService { - public IServiceStateObserver ServiceStateObserver { get; } - public IState IntState { get; } + public IObservableStateProvider ObservableStateVoidProvider { get; } + public IObservableStateProvider ObservableStateIntProvider { get; } + public IState ObservableIntState { get; } + public IState IntState { get; } public IState IntStateAsyncX { get; } public IState, List> CRUDListState { get; } @@ -17,8 +20,8 @@ public partial class ServiceFixture : RxBLService public IStateProvider Increment { get; } public IStateTransformer Add { get; } - public IServiceStateTransformer ChangeTest { get; } - public IServiceStateProvider ChangeTestSync { get; } + public IStateProvider ChangeTestAsync { get; } + public IStateProvider ChangeTestSync { get; } public IStateTransformer<(IntListVP.CMD_LIST CMD, CRUDTest? ITEM)> CRUDListCmds { get; } public IStateTransformer<(IntDictVP.CMD_DICT CMD, Guid? ID, CRUDTest? ITEM)> CRUDDictCmds { get; } @@ -27,7 +30,9 @@ public partial class ServiceFixture : RxBLService public ServiceFixture() { - ServiceStateObserver = this.CreateStateObserver(); + ObservableStateVoidProvider = this.CreateObservableStateProvider(); + ObservableIntState = this.CreateState(0); + ObservableStateIntProvider = this.CreateObservableStateProvider(ObservableIntState); IntState = this.CreateState(-1); @@ -41,7 +46,7 @@ public ServiceFixture() Increment = new IncremementVP(this, IntState); Add = new AddVP(this, IntState); - ChangeTest = new ChangeTestSP(this); + ChangeTestAsync = new ChangeTestSP(this); ChangeTestSync = new ChangeTestSyncSP(this); } diff --git a/RxBlazorLightCoreTests/StateTests.cs b/RxBlazorLightCoreTests/StateTests.cs index 35388e1..3f66d97 100644 --- a/RxBlazorLightCoreTests/StateTests.cs +++ b/RxBlazorLightCoreTests/StateTests.cs @@ -3,6 +3,7 @@ using Xunit.Abstractions; using System.Reactive.Linq; using System.Reactive; +using System; namespace RxBlazorLightCoreTests { @@ -163,14 +164,14 @@ public void TestState() fixture.Subscribe(sc => { - if (sc.ID == fixture.ChangeTest.ID) + if (sc.ID == fixture.ChangeTestAsync.ID) { stateChangeCount++; } - _output.WriteLine($"Done {fixture.ChangeTest.Done()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ChangeTest.Phase}, ID {sc.ID}, VPID {fixture.ChangeTest.ID}"); + _output.WriteLine($"Done {fixture.ChangeTestAsync.Done()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ChangeTestAsync.Phase}, ID {sc.ID}, VPID {fixture.ChangeTestAsync.ID}"); - if (fixture.ChangeTest.Done()) + if (fixture.ChangeTestAsync.Done()) { done = true; } @@ -180,10 +181,10 @@ public void TestState() Assert.Equal(string.Empty, fixture.Test); - fixture.ChangeTest.Transform("Test"); + fixture.ChangeTestAsync.Provide(); while (!done) ; - Assert.Equal("Test", fixture.Test); + Assert.Equal("Async", fixture.Test); Assert.True(stateChangeCount > 0 && stateChangeCount <= 2); } @@ -230,18 +231,18 @@ public void TestStateCancel() fixture.Subscribe(sc => { - if (sc.ID == fixture.ChangeTest.ID) + if (sc.ID == fixture.ChangeTestAsync.ID) { stateChangeCount++; - if (fixture.ChangeTest.Canceled()) + if (fixture.ChangeTestAsync.Canceled()) { canceled = true; } - _output.WriteLine($"Done {fixture.ChangeTest.Done()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ChangeTest.Phase}, ID {sc.ID}, VPID {fixture.ChangeTest.ID}"); + _output.WriteLine($"Done {fixture.ChangeTestAsync.Done()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ChangeTestAsync.Phase}, ID {sc.ID}, VPID {fixture.ChangeTestAsync.ID}"); - if (fixture.ChangeTest.Done()) + if (fixture.ChangeTestAsync.Done()) { done = true; } @@ -251,8 +252,8 @@ public void TestStateCancel() fixture.ClearTest(); Assert.Equal(string.Empty, fixture.Test); - fixture.ChangeTest.Transform("Test"); - fixture.ChangeTest.Cancel(); + fixture.ChangeTestAsync.Provide(); + fixture.ChangeTestAsync.Cancel(); while (!done) ; @@ -270,14 +271,14 @@ public void TestStateObservable() fixture.Subscribe(sc => { - if (sc.ID == fixture.ServiceStateObserver.ID) + if (sc.ID == fixture.ObservableStateVoidProvider.ID) { stateChangeCount++; } - _output.WriteLine($"Canceled {fixture.ServiceStateObserver.Canceled()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ServiceStateObserver.Phase}, ID {sc.ID}, VPID {fixture.ServiceStateObserver.ID}"); + _output.WriteLine($"Completed {fixture.ObservableStateVoidProvider.Completed()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ObservableStateVoidProvider.Phase}, ID {sc.ID}, VPID {fixture.ObservableStateVoidProvider.ID}"); - if (fixture.ServiceStateObserver.Canceled()) + if (fixture.ObservableStateVoidProvider.Completed()) { completed = true; } @@ -285,7 +286,7 @@ public void TestStateObservable() var changer = Observable.Range(0, 2); - var disposable = changer.Select(_ => Unit.Default).Subscribe(fixture.ServiceStateObserver); + var disposable = changer.Select(_ => Unit.Default).Subscribe(fixture.ObservableStateVoidProvider); while (!completed) ; @@ -301,30 +302,61 @@ public void TestStateObservableException() fixture.Subscribe(sc => { - if (sc.ID == fixture.ServiceStateObserver.ID) + if (sc.ID == fixture.ObservableStateVoidProvider.ID) { stateChangeCount++; } - _output.WriteLine($"Exception {fixture.ServiceStateObserver.Exception()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ServiceStateObserver.Phase}, ID {sc.ID}, VPID {fixture.ServiceStateObserver.ID}"); + _output.WriteLine($"Exception {fixture.ObservableStateVoidProvider.Exception()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ObservableStateVoidProvider.Phase}, ID {sc.ID}, VPID {fixture.ObservableStateVoidProvider.ID}"); - if (fixture.ServiceStateObserver.Exception()) + if (fixture.ObservableStateVoidProvider.Exception()) { exception = true; } }, 0); - fixture.ServiceStateObserver.Provide(); + fixture.ObservableStateVoidProvider.OnNext(Unit.Default); var changer = Observable.Throw(new InvalidOperationException("Test")); - var disposable = changer.Select(_ => Unit.Default).Subscribe(fixture.ServiceStateObserver); + var disposable = changer.Select(_ => Unit.Default).Subscribe(fixture.ObservableStateVoidProvider); while (!exception) ; Assert.Equal(2, stateChangeCount); } + [Fact] + public void TestStateIntObservable() + { + ServiceFixture fixture = new(); + var stateChangeCount = 0; + bool completed = false; + + fixture.Subscribe(sc => + { + if (sc.ID == fixture.ObservableStateIntProvider.ID) + { + stateChangeCount++; + } + + _output.WriteLine($"Completed {fixture.ObservableStateIntProvider.Completed()}, CC {stateChangeCount} Reason {sc.Reason}, Phase {fixture.ObservableStateIntProvider.Phase}, ID {sc.ID}, VPID {fixture.ObservableStateIntProvider.ID}"); + + if (fixture.ObservableStateIntProvider.Completed()) + { + completed = true; + } + }, 0); + + var changer = Observable.Range(0, 3); + var disposable = changer.Subscribe(fixture.ObservableStateIntProvider); + + while (!completed) ; + + Assert.Equal(2, fixture.ObservableIntState.Value); + Assert.Equal(4, stateChangeCount); + } + [Fact] public void TestCRUDList() { diff --git a/RxMudBlazorLight/RxMudBlazorLight.csproj b/RxMudBlazorLight/RxMudBlazorLight.csproj index d6eacfb..01c3786 100644 --- a/RxMudBlazorLight/RxMudBlazorLight.csproj +++ b/RxMudBlazorLight/RxMudBlazorLight.csproj @@ -20,7 +20,7 @@ Blazor,MudBlazor,Rx,Reactive true ..\Nuget - 0.9.4 + 0.9.5 diff --git a/RxMudBlazorLightTestBase/Components/TestPlayground.razor b/RxMudBlazorLightTestBase/Components/TestPlayground.razor index 575ce4e..04e8828 100644 --- a/RxMudBlazorLightTestBase/Components/TestPlayground.razor +++ b/RxMudBlazorLightTestBase/Components/TestPlayground.razor @@ -13,9 +13,9 @@ Increment Add 5 @($"Equals executions {_equalsExecutions}") - EqualsTest + EqualsTest @($"Equals async executions {_equalsAsyncExecutions}") - EqualsTestAsync + EqualsTestAsync @if (Service.Exceptions.Any()) @@ -34,7 +34,7 @@ protected override void ServiceStateHasChanged(Guid id, ChangeReason changeReason) { - if (id == Service.EqualsTest.ID && Service.EqualsTest.Changed()) + if (id == Service.EqualsTestSync.ID && Service.EqualsTestSync.Changed()) { _equalsExecutions++; StateHasChanged(); diff --git a/RxMudBlazorLightTestBase/Service/TestService.ProvidersTransformers.cs b/RxMudBlazorLightTestBase/Service/TestService.ProvidersTransformers.cs index 8a61a22..33d8d85 100644 --- a/RxMudBlazorLightTestBase/Service/TestService.ProvidersTransformers.cs +++ b/RxMudBlazorLightTestBase/Service/TestService.ProvidersTransformers.cs @@ -1,11 +1,13 @@ using RxBlazorLightCore; +using static MudBlazor.Colors; +using System.Threading; using static RxMudBlazorLightTestBase.Service.TimerService; namespace RxMudBlazorLightTestBase.Service { public sealed partial class TestService { - public class EqualsTestVP(TestService service) : ServiceStateProvider(service) + public class EqualsTestSyncSP(TestService service) : StateProvider(service) { protected override bool CanProvide() { @@ -18,17 +20,17 @@ protected override void ProvideState() } } - public class EqualsTestSPAsync(TestService service) : ServiceStateTransformerAsync(service) + public class EqualsTestAsyncSP(TestService service) : StateProviderAsync(service) { - public override bool CanTransform(int _) + protected override bool CanProvide() { return Service._equalTestAsyncValue < 2; } - protected override async Task TransformStateAsync(int value, CancellationToken cancellationToken) + protected override async Task ProvideStateAsync(CancellationToken cancellationToken) { await Task.Delay(500, cancellationToken); - Service._equalTestAsyncValue += value; + Service._equalTestAsyncValue += 10; } } diff --git a/RxMudBlazorLightTestBase/Service/TestService.cs b/RxMudBlazorLightTestBase/Service/TestService.cs index 62e4cdc..80f284a 100644 --- a/RxMudBlazorLightTestBase/Service/TestService.cs +++ b/RxMudBlazorLightTestBase/Service/TestService.cs @@ -83,8 +83,8 @@ public class Scope(TestService service) : BaseScope(service) public IStateProvider Increment { get; } public IStateTransformer AddAsync { get; } - public IServiceStateProvider EqualsTest { get; } - public IServiceStateTransformer EqualsTestAsync { get; } + public IStateProvider EqualsTestSync { get; } + public IStateProvider EqualsTestAsync { get; } public IStateTransformer Add { get; } public IStateProvider IncrementAsync { get; } @@ -116,8 +116,8 @@ public TestService(IServiceProvider sp) : base(sp) Increment = new IncrementVP(this, CountState); AddAsync = new AddSPAsync(this, CountState); - EqualsTest = new EqualsTestVP(this); - EqualsTestAsync = new EqualsTestSPAsync(this); + EqualsTestSync = new EqualsTestSyncSP(this); + EqualsTestAsync = new EqualsTestAsyncSP(this); Exception = new ExceptionVP(this, CountState);