Skip to content

Commit

Permalink
When mutating, raise an event with a message explaining what the chan…
Browse files Browse the repository at this point in the history
…ge has been. #17
  • Loading branch information
rdghm1234 committed Jul 9, 2023
1 parent 7d410f6 commit e985ee5
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 94 deletions.
25 changes: 17 additions & 8 deletions samples/Memento.Sample.Blazor/Stores/AsyncCounterStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,53 @@ public record AsyncCounterState {
public ImmutableArray<int> Histories { get; init; } = ImmutableArray.Create<int>();
}

public class AsyncCounterStore : Store<AsyncCounterState> {
public AsyncCounterStore() : base(() => new()) { }
public enum StateChangedType {
CountUp,
Loading,
CountUpAsync,
SetCount,
CountUpWithAmount
}

public class AsyncCounterStore : Store<AsyncCounterState, StateChangedType> {
public AsyncCounterStore() : base(() => new()) {
}

public void CountUp() {
Mutate(state => state with {
Count = state.Count + 1,
IsLoading = false,
Histories = state.Histories.Add(state.Count + 1),
});
}, StateChangedType.CountUp);
}

public async Task CountUpAsync() {
Mutate(state => state with { IsLoading = true, });
Mutate(state => state with { IsLoading = true, }, StateChangedType.Loading);
await Task.Delay(800);
Mutate(state => state with {
Count = state.Count + 1,
IsLoading = false,
Histories = state.Histories.Add(state.Count + 1),
});
}, StateChangedType.CountUp);
}

public void CountUpManyTimes(int count) {
for (var i = 0; i < count; i++) {
Mutate(state => state with {
Count = state.Count + 1,
});
}, StateChangedType.CountUpAsync);
}
}

public void SetCount(int count) {
Mutate(state => state with {
Count = count,
});
}, StateChangedType.SetCount);
}

public void CountUpWithAmount(int amount) {
Mutate(state => state with {
Count = state.Count + amount,
});
}, StateChangedType.CountUpWithAmount);
}
}
2 changes: 1 addition & 1 deletion samples/Memento.Sample.Blazor/Stores/RedoUndoTodoStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public record RedoUndoTodoState {
public bool IsLoading { get; init; }
}

public class RedoUndoTodoStore : MementoStore<RedoUndoTodoState> {
public class RedoUndoTodoStore : MementoStore<RedoUndoTodoState, string> {
ITodoService TodoService { get; }

public RedoUndoTodoStore(ITodoService todoService)
Expand Down
2 changes: 1 addition & 1 deletion samples/Memento.Sample.ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
// Observe a store state
store.Subscribe(e => {
Console.WriteLine();
Console.WriteLine($"// {e.Command.GetType().Name}");
Console.WriteLine($"// {e.Command?.GetType().Name}");
Console.WriteLine(JsonSerializer.Serialize(
e.State,
new JsonSerializerOptions() {
Expand Down
29 changes: 15 additions & 14 deletions src/Memento.Core/AbstractStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Memento.Core;
/// <typeparam name="TState">The type of the state managed by the store.</typeparam>
/// <typeparam name="TCommand">The type of the commands used to mutate the state.</typeparam>
public abstract class AbstractStore<TState, TCommand>
: IStore, IObservable<StateChangedEventArgs<TState>>, IDisposable
: IStore, IObservable<StateChangedEventArgs<TState, TCommand>>, IDisposable
where TState : class
where TCommand : Command {
readonly List<IObserver<StateChangedEventArgs>> _observers = new();
Expand Down Expand Up @@ -92,8 +92,8 @@ protected virtual IEnumerable<IDisposable> OnHandleDisposable() {
/// </summary>
/// <param name="observer">The observer to subscribe to the store.</param>
/// <returns>An IDisposable instance that can be used to unsubscribe from the store.</returns>
public IDisposable Subscribe(Action<StateChangedEventArgs<TState>> observer) {
return Subscribe(new GeneralObserver<StateChangedEventArgs<TState>>(observer));
public IDisposable Subscribe(Action<StateChangedEventArgs<TState, TCommand>> observer) {
return Subscribe(new GeneralObserver<StateChangedEventArgs<TState, TCommand>>(observer));
}

/// <summary>
Expand All @@ -115,9 +115,9 @@ public TStore AsStore<TStore>() where TStore : IStore {
/// </summary>
/// <param name="observer">The observer to subscribe to the store.</param>
/// <returns>An IDisposable instance that can be used to unsubscribe from the store.</returns>
public IDisposable Subscribe(IObserver<StateChangedEventArgs<TState>> observer) {
public IDisposable Subscribe(IObserver<StateChangedEventArgs<TState, TCommand>> observer) {
var obs = new StoreObserver(e => {
if (e is StateChangedEventArgs<TState> o) {
if (e is StateChangedEventArgs<TState, TCommand> o) {
observer.OnNext(o);
}
});
Expand All @@ -139,10 +139,11 @@ public IDisposable Subscribe(IObserver<StateChangedEventArgs<TState>> observer)
/// via <see cref="FluxStore{TState, TCommand}.Dispatch"/> or <see cref="Store{TState}.Mutate"/>.
/// </summary>
public void StateHasChanged() {
InvokeObserver(new StateChangedEventArgs<TState>() {
InvokeObserver(new StateChangedEventArgs<TState, TCommand>() {
State = State,
LastState = State,
Command = new Command.StateHasChanged(State),
Command = null,
StateChangeType = StateChangeType.StateHasChanged,
Sender = this,
});
}
Expand Down Expand Up @@ -178,9 +179,9 @@ void IStore.SetStateForce(object state) {

var previous = State;
State = tState;
var command = new Command.ForceReplaced(State);
InvokeObserver(new StateChangedEventArgs<TState>() {
Command = command,
InvokeObserver(new StateChangedEventArgs<TState, TCommand>() {
Command = null,
StateChangeType = StateChangeType.ForceReplaced,
LastState = previous,
Sender = this,
State = State,
Expand Down Expand Up @@ -232,13 +233,13 @@ internal void ComputedAndApplyState(TState state, TCommand command) {
}
}

(TState?, StateChangedEventArgs<TState>?) ComputeNewState() {
(TState?, StateChangedEventArgs<TState, TCommand>?) ComputeNewState() {
var previous = state;
var postState = OnBeforeDispatch(previous, command);

if (MiddlewareHandler.Invoke(postState, command) is TState s) {
var newState = OnAfterDispatch(s, command);
var e = new StateChangedEventArgs<TState> {
var e = new StateChangedEventArgs<TState, TCommand> {
LastState = previous,
Command = command,
State = newState,
Expand Down Expand Up @@ -299,8 +300,8 @@ async Task IStore.InitializeAsync(StoreProvider provider) {
);
}

internal void InvokeObserver(StateChangedEventArgs<TState> e) {
foreach (var obs in _observers) {
internal void InvokeObserver(StateChangedEventArgs<TState, TCommand> e) {
foreach (var obs in _observers.ToArray()) {
obs.OnNext(e);
}
}
Expand Down
66 changes: 58 additions & 8 deletions src/Memento.Core/Command.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,73 @@
using System.Text.Json.Serialization;

namespace Memento.Core;

public enum StateHasChangedType {
StateHasChanged,
ForceReplaced,
Restored,
}

/// <summary>
/// Represents an abstract base class for commands that mutate the state of a store.
/// </summary>
public abstract record Command {
public record Command {
/// <summary>
/// Represents a command that forces a state replacement.
/// Represents a command that indicates a state change has occurred.
/// </summary>
public record ForceReplaced(object State) : Command;
public record StateHasChanged : Command {
public StateHasChangedType StateHasChangedType { get; internal set; }

/// <summary>
/// Represents a command that restores the previous state.
/// </summary>
public record Restored : Command;
public object State { get; }

public object? Message { get; }

[JsonIgnore]
public Type? StoreType { get; } = null;

public override string Type => $"{StoreType?.Name ?? "Store"}+{GetType().Name}";

public StateHasChanged(object state, object? message = null, Type? storeType = null) {
State = state;
Message = message;
StoreType = storeType;
}

public static StateHasChanged CreateForceReplaced(object State) => new(State) {
StateHasChangedType = StateHasChangedType.ForceReplaced
};

public static StateHasChanged CreateRestored(object State) => new(State) {
StateHasChangedType = StateHasChangedType.Restored
};
}

/// <summary>
/// Represents a command that indicates a state change has occurred.
/// </summary>
public record StateHasChanged(object State) : Command;
public record StateHasChanged<TState, TMessage> : StateHasChanged
where TState : notnull
where TMessage : notnull {

public new TState State => (TState)base.State;

public new TMessage? Message => (TMessage?)base.Message;

public override string Type => $"{StoreType?.Name ?? "Store"}+{GetType().Name}";

public StateHasChanged(TState mutateState, TMessage? message = default, Type? storeType = null)
: base(mutateState, message, storeType) {

}

public static StateHasChanged<TState, TMessage> CreateForceReplaced(TState State) => new(State) {
StateHasChangedType = StateHasChangedType.ForceReplaced
};

public static StateHasChanged<TState, TMessage> CreateRestored(TState State) => new(State) {
StateHasChangedType = StateHasChangedType.Restored
};
}

/// <summary>
/// Gets the type of the command, excluding the assembly name.
Expand Down
5 changes: 3 additions & 2 deletions src/Memento.Core/FluxMementoStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@ await _historyManager.ExcuteCommitAsync(

var lastState = State;
State = state.State;
InvokeObserver(new StateChangedEventArgs<TState> {
InvokeObserver(new StateChangedEventArgs<TState,TCommand> {
LastState = lastState,
State = State,
Command = new Command.Restored(),
Command = null,
StateChangeType = StateChangeType.Restored,
Sender = this,
});
},
Expand Down
18 changes: 13 additions & 5 deletions src/Memento.Core/MementoStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ namespace Memento.Core;

public record MementoStoreContext<TState>(TState State);

public abstract class MementoStore<TState>
: Store<TState>
where TState : class {
public abstract class MementoStore<TState, TMessage>
: Store<TState, TMessage>
where TState : class
where TMessage : notnull {
readonly HistoryManager _historyManager;

public bool CanReDo => _historyManager.CanReDo;
Expand Down Expand Up @@ -84,10 +85,11 @@ await _historyManager.ExcuteCommitAsync(

var lastState = State;
State = state.State;
InvokeObserver(new StateChangedEventArgs<TState> {
InvokeObserver(new StateChangedEventArgs<TState, Command.StateHasChanged<TState, TMessage>> {
LastState = lastState,
State = State,
Command = new Command.Restored(),
Command = null,
StateChangeType = StateChangeType.Restored,
Sender = this,
});
},
Expand Down Expand Up @@ -128,4 +130,10 @@ public async ValueTask ReExecuteAsync() {

await _historyManager.ReExecuteAsync();
}
}

public class MementoStore<TState> : MementoStore<TState, string>
where TState : class {
public MementoStore(StateInitializer<TState> initializer, HistoryManager historyManager) : base(initializer, historyManager) {
}
}
25 changes: 20 additions & 5 deletions src/Memento.Core/StateChangedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
namespace Memento.Core;

public enum StateChangeType {
Command,
StateHasChanged,
ForceReplaced,
Restored,
}

public record StateChangedEventArgs {
protected object? sender;

public Command Command { get; init; } = default!;
public StateChangeType StateChangeType { get; init; }

public Command? Command { get; init; } = default!;

public object? LastState { get; init; }

Expand All @@ -21,15 +30,21 @@ public IStore? Sender {
}
}

public record StateChangedEventArgs<TState> : StateChangedEventArgs
where TState : class {
public record StateChangedEventArgs<TState, TCommand> : StateChangedEventArgs
where TState : class
where TCommand : Command {

public new TCommand? Command {
get => (TCommand)base.Command!;
init => base.Command = value;
}

public new required TState LastState {
public new required TState? LastState {
get => (TState)base.LastState!;
init => base.LastState = value;
}

public new required TState State {
public new required TState? State {
get => (TState)base.State!;
init => base.State = value;
}
Expand Down
Loading

0 comments on commit e985ee5

Please sign in to comment.