Skip to content

Commit bd9780b

Browse files
committed
added bufferedThrottle suggested in fabulous-dev/Fabulous#1070
1 parent 9185592 commit bd9780b

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

Ui/App.fs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,74 @@ module App =
175175

176176
command
177177

178+
/// <summary>
179+
/// Creates a Command factory that dispatches the most recent message in a given interval - even if delayed.
180+
/// This makes it similar to <see cref="throttle"/> in that it rate-limits the message dispatch
181+
/// and similar to <see cref="debounce"/> in that it guarantees the last message (within the interval or in total) is dispatched.
182+
/// Helpful for scenarios where you want to throttle, but cannot risk losing the last message to throttling
183+
/// - like the last progress update that completes a progress.
184+
/// Note that this function creates an object with internal state and is intended to be used per Program or longer-running background process
185+
/// rather than once per message in the update function.
186+
/// </summary>
187+
/// <param name="interval">The minimum time interval between two consecutive Command executions in milliseconds.</param>
188+
/// <param name="fn">A function that maps a factory input value to a message for dispatch.</param>
189+
/// <returns>
190+
/// A Command factory function that maps an input value to a "buffered throttle" Command which dispatches the most recent message (mapped from the value)
191+
/// if the minimum time interval has elapsed since the last Command execution; otherwise, it does nothing.
192+
/// </returns>
193+
let bufferedThrottle (interval: int) (fn: 'value -> 'msg) : 'value -> Cmd<'msg> =
194+
let funLock = obj() // ensures safe access to resources shared across different threads
195+
let mutable lastDispatch = System.DateTime.MinValue
196+
let mutable cts: CancellationTokenSource = null // if set, allows cancelling the last issued Command
197+
198+
// Return a factory function mapping input values to buffered throttled Commands with delayed dispatch of the most recent message
199+
fun (value: 'value) ->
200+
[ fun dispatch ->
201+
// Lock to ensure thread-safe access to shared resources
202+
lock funLock (fun () ->
203+
let now = System.DateTime.UtcNow
204+
let elapsedSinceLastDispatch = now - lastDispatch
205+
let rateLimit = System.TimeSpan.FromMilliseconds(float interval)
206+
207+
// If the interval has elapsed since the last dispatch, dispatch immediately
208+
if elapsedSinceLastDispatch >= rateLimit then
209+
lastDispatch <- now
210+
dispatch(fn value)
211+
else // schedule the dispatch for when the interval is up
212+
// cancel the last sleeping Command issued earlier from this factory
213+
if cts <> null then
214+
cts.Cancel()
215+
cts.Dispose()
216+
217+
// make cancellation available to the factory's next Command
218+
cts <- new CancellationTokenSource()
219+
220+
// asynchronously wait for the remaining time before dispatch
221+
Async.Start(
222+
async {
223+
do! Async.Sleep(rateLimit - elapsedSinceLastDispatch)
224+
225+
lock funLock (fun () ->
226+
dispatch(fn value)
227+
228+
// done; invalidate own cancellation token
229+
if cts <> null then
230+
cts.Dispose()
231+
cts <- null)
232+
},
233+
cts.Token
234+
)
235+
)
236+
]
237+
178238
let private searchCmd model =
179239
fun dispatch ->
180240
async {
181241
let command = mapToSearchCommand model
182242
let cacheFolder = Folder.GetPath Folders.cache
183243
let dataStore = JsonFileDataStore cacheFolder
184244
let youtube = Youtube(dataStore, VideoIndexRepository cacheFolder)
185-
let dispatchProgress = Cmd.debounce 100 (fun progress ->
245+
let dispatchProgress = bufferedThrottle 100 (fun progress ->
186246
System.Diagnostics.Debug.WriteLine("############# progress dispatched" + Environment.NewLine + progress.ToString())
187247
SearchProgress progress)
188248
command.SetProgressReporter(Progress<BatchProgress>(fun progress ->

0 commit comments

Comments
 (0)