Skip to content

Commit 950333e

Browse files
committed
Feat: Add support for empty state in debouncers
1 parent 5364da4 commit 950333e

File tree

5 files changed

+48
-8
lines changed

5 files changed

+48
-8
lines changed

src/CodeOfChaos.Extensions.AspNetCore.Components/EventCallbacks/EventCallbackDebouncer.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,18 @@ namespace CodeOfChaos.Extensions.Debouncers;
88
// ---------------------------------------------------------------------------------------------------------------------
99
// Code
1010
// ---------------------------------------------------------------------------------------------------------------------
11+
// ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
1112
public sealed class EventCallbackDebouncer : DebouncerBase<EventCallbackDebouncer.EmptyUnit> {
1213
public readonly struct EmptyUnit;
1314
private EventCallback Callback { get; init; }
14-
15+
16+
private bool _isEmpty;
17+
protected override bool IsEmpty => _isEmpty;
18+
public static EventCallbackDebouncer Empty => new() {
19+
Callback = default,
20+
_isEmpty = true
21+
};
22+
1523
// -----------------------------------------------------------------------------------------------------------------
1624
// Constructors
1725
// -----------------------------------------------------------------------------------------------------------------
@@ -26,16 +34,24 @@ public static EventCallbackDebouncer FromEventCallback(EventCallback callback, i
2634
// -----------------------------------------------------------------------------------------------------------------
2735
public Task InvokeDebouncedAsync(CancellationToken ct = default)
2836
=> DebouncerLogicAsync(default, ct);
29-
37+
3038
protected async override ValueTask InvokeCallbackAsync(EmptyUnit item, CancellationToken ct = default) {
3139
if (ct.IsCancellationRequested) return;
3240
await Callback.InvokeAsync();
3341
}
3442
}
3543

44+
// ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
3645
public sealed class EventCallbackDebouncer<T> : DebouncerBase<T> {
3746
private EventCallback<T> Callback { get; init; }
3847

48+
private bool _isEmpty;
49+
protected override bool IsEmpty => _isEmpty;
50+
public static EventCallbackDebouncer<T> Empty => new() {
51+
Callback = default,
52+
_isEmpty = true
53+
};
54+
3955
// -----------------------------------------------------------------------------------------------------------------
4056
// Constructors
4157
// -----------------------------------------------------------------------------------------------------------------

src/CodeOfChaos.Extensions/Debouncers/Regular/Debouncer.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ namespace CodeOfChaos.Extensions.Debouncers;
88
// ---------------------------------------------------------------------------------------------------------------------
99
public sealed class Debouncer: DebouncerBase<Debouncer.EmptyUnit> {
1010
public readonly struct EmptyUnit; // Workaround for reusing DebouncerBase<T> instead of creating a fully new DebouncerBase without generics
11-
private object Callback { get; init; } = null!;
11+
private object? Callback { get; init; }
12+
protected override bool IsEmpty => Callback is null;
13+
14+
public static Debouncer Empty => new() {
15+
Callback = null
16+
};
1217

1318
// -----------------------------------------------------------------------------------------------------------------
1419
// Constructors
@@ -64,7 +69,12 @@ protected async override ValueTask InvokeCallbackAsync(EmptyUnit item, Cancellat
6469
}
6570

6671
public sealed class Debouncer<T> : DebouncerBase<T> {
67-
private object Callback { get; init; } = null!;
72+
private object? Callback { get; init; }
73+
protected override bool IsEmpty => Callback is null;
74+
75+
public static Debouncer<T> Empty => new() {
76+
Callback = null
77+
};
6878

6979
// -----------------------------------------------------------------------------------------------------------------
7080
// Constructors

src/CodeOfChaos.Extensions/Debouncers/Regular/DebouncerBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public abstract class DebouncerBase<T> : IAsyncDisposable {
1616
private bool _isDisposed;
1717
private T? _latestValue;
1818

19+
protected abstract bool IsEmpty { get; }
20+
1921
// -----------------------------------------------------------------------------------------------------------------
2022
// Methods
2123
// -----------------------------------------------------------------------------------------------------------------
@@ -24,6 +26,7 @@ public abstract class DebouncerBase<T> : IAsyncDisposable {
2426
// ReSharper disable once PossiblyMistakenUseOfCancellationToken
2527
protected async Task DebouncerLogicAsync(T? value = default, CancellationToken ct = default) {
2628
ObjectDisposedException.ThrowIf(_isDisposed, this);
29+
if (IsEmpty) return;
2730

2831
await _semaphore.WaitAsync(ct);
2932
try {

src/CodeOfChaos.Extensions/Debouncers/Throttled/ThrottledDebouncer.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ namespace CodeOfChaos.Extensions.Debouncers;
88
// ---------------------------------------------------------------------------------------------------------------------
99
public sealed class ThrottledDebouncer: ThrottledDebouncerBase<ThrottledDebouncer.EmptyUnit> {
1010
public readonly struct EmptyUnit; // Workaround for reusing ThrottledDebouncerBase<T> instead of creating a fully new DebouncerBase without generics
11-
private object Callback { get; init; } = null!;
11+
private object? Callback { get; init; }
12+
13+
protected override bool IsEmpty => Callback is null;
14+
public static ThrottledDebouncer Empty => new() {
15+
Callback = null
16+
};
1217

1318
// -----------------------------------------------------------------------------------------------------------------
1419
// Constructors
@@ -40,7 +45,7 @@ public static ThrottledDebouncer FromDelegate(Func<Task> func, int debounceMs =
4045
// -----------------------------------------------------------------------------------------------------------------
4146
public Task InvokeDebouncedAsync(CancellationToken ct = default)
4247
=> DebouncerLogicAsync(default, ct);
43-
48+
4449
protected async override ValueTask InvokeCallbackAsync(EmptyUnit item, CancellationToken ct = default) {
4550
if (ct.IsCancellationRequested) return;
4651

@@ -67,7 +72,11 @@ protected async override ValueTask InvokeCallbackAsync(EmptyUnit item, Cancellat
6772
}
6873

6974
public sealed class ThrottledDebouncer<T> : ThrottledDebouncerBase<T> {
70-
private object Callback { get; init; } = null!;
75+
private object? Callback { get; init; }
76+
protected override bool IsEmpty => Callback is null;
77+
public static ThrottledDebouncer<T> Empty => new() {
78+
Callback = null
79+
};
7180

7281
// -----------------------------------------------------------------------------------------------------------------
7382
// Constructors

src/CodeOfChaos.Extensions/Debouncers/Throttled/ThrottledDebouncerBase.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ public abstract class ThrottledDebouncerBase<T> : IAsyncDisposable {
1919
private bool _isDisposed;
2020
private T? _latestValue;
2121
private DateTime _lastExecuteTime = DateTime.MinValue;
22-
private DateTime _firstCallTime = DateTime.MinValue;
22+
private DateTime _firstCallTime = DateTime.MinValue;
2323

24+
protected abstract bool IsEmpty { get; }
2425

2526
// -----------------------------------------------------------------------------------------------------------------
2627
// Methods
@@ -30,6 +31,7 @@ public abstract class ThrottledDebouncerBase<T> : IAsyncDisposable {
3031
// ReSharper disable twice PossiblyMistakenUseOfCancellationToken
3132
protected async Task DebouncerLogicAsync(T? value = default, CancellationToken ct = default) {
3233
ObjectDisposedException.ThrowIf(_isDisposed, this);
34+
if (IsEmpty) return;
3335

3436
await _semaphore.WaitAsync(ct);
3537
try {

0 commit comments

Comments
 (0)