@@ -325,17 +325,23 @@ module Cmd =
325325 /// <param name =" interval " >The minimum time interval between two consecutive Command executions in milliseconds.</param >
326326 /// <param name =" fn " >A function that maps a list of factory input values to a message for dispatch.</param >
327327 /// <returns >
328- /// A Command factory function that maps a list of input values to a Command which dispatches a message (mapped from the pending values),
328+ /// Two methods - the first being a Command factory function that maps a list of input values to a Command
329+ /// which dispatches a message (mapped from the pending values),
329330 /// either immediately or after a delay respecting the interval, while remembering and dispatching all remembered values
330331 /// when the interval has elapsed, ensuring no values are lost.
332+ /// The second can be used for awaiting the next dispatch from the outside while adding some buffer time.
331333 /// </returns >
332- let batchedThrottle ( interval : int ) ( mapValuesToMsg : 'value list -> 'msg ) : 'value -> Cmd < 'msg > =
334+ let batchedThrottle ( interval : int ) ( mapValuesToMsg : 'value list -> 'msg ) : ( 'value -> Cmd < 'msg >) * ( System.TimeSpan option -> Async < unit >) =
333335 let rateLimit = System.TimeSpan.FromMilliseconds( float interval)
334336 let funLock = obj() // ensures safe access to resources shared across different threads
335337 let mutable lastDispatch = System.DateTime.MinValue
336338 let mutable pendingValues : 'value list = []
337339 let mutable cts : CancellationTokenSource = null // if set, allows cancelling the last issued Command
338340
341+ // gets the time to wait until the next allowed dispatch returning a negative timespan if the time is up
342+ let getTimeUntilNextDispatch () =
343+ lastDispatch.Add( rateLimit) - System.DateTime.UtcNow
344+
339345 // dispatches all pendingValues and resets them while updating lastDispatch
340346 let dispatchBatch ( dispatch : 'msg -> unit ) =
341347 // Dispatch in the order they were received
@@ -344,39 +350,57 @@ module Cmd =
344350 lastDispatch <- System.DateTime.UtcNow
345351 pendingValues <- []
346352
347- // Return a factory function mapping input values to sleeping Commands dispatching all pending messages
348- fun ( value : 'value ) ->
349- [ fun dispatch ->
350- lock funLock ( fun () ->
351- let now = System.DateTime.UtcNow
352- let elapsedSinceLastDispatch = now - lastDispatch
353- pendingValues <- value :: pendingValues
354-
355- // If the interval has elapsed since the last dispatch, dispatch all pending messages
356- if elapsedSinceLastDispatch >= rateLimit then
357- dispatchBatch dispatch
358- else // schedule dispatch
359-
360- // if the the last sleeping dispatch can still be cancelled, do so
361- if cts <> null then
362- cts.Cancel()
363- cts.Dispose()
364-
365- // used to enable cancelling this dispatch if newer values come into the factory
366- cts <- new CancellationTokenSource()
367-
368- Async.Start(
369- async {
370- // wait only as long as we have to before next dispatch
371- do ! Async.Sleep( rateLimit - elapsedSinceLastDispatch)
372-
373- lock funLock ( fun () ->
374- dispatchBatch dispatch
375-
376- // done; invalidate own cancellation
377- if cts <> null then
378- cts.Dispose()
379- cts <- null )
380- },
381- cts.Token
382- )) ]
353+ // a factory function mapping input values to sleeping Commands dispatching all pending messages
354+ let factory =
355+ fun ( value : 'value ) ->
356+ [ fun dispatch ->
357+ lock funLock ( fun () ->
358+ let untilNextDispatch = getTimeUntilNextDispatch()
359+ pendingValues <- value :: pendingValues
360+
361+ // If the interval has elapsed since the last dispatch, dispatch all pending messages
362+ if untilNextDispatch <= System.TimeSpan.Zero then
363+ dispatchBatch dispatch
364+ else // schedule dispatch
365+
366+ // if the the last sleeping dispatch can still be cancelled, do so
367+ if cts <> null then
368+ cts.Cancel()
369+ cts.Dispose()
370+
371+ // used to enable cancelling this dispatch if newer values come into the factory
372+ cts <- new CancellationTokenSource()
373+
374+ Async.Start(
375+ async {
376+ // wait only as long as we have to before next dispatch
377+ do ! Async.Sleep( untilNextDispatch)
378+
379+ lock funLock ( fun () ->
380+ dispatchBatch dispatch
381+
382+ // done; invalidate own cancellation
383+ if cts <> null then
384+ cts.Dispose()
385+ cts <- null )
386+ },
387+ cts.Token
388+ )) ]
389+
390+ // a function to wait until after the next async dispatch + some buffer time to ensure the dispatch is complete
391+ let awaitNextDispatch buffer =
392+ lock funLock ( fun () ->
393+ async {
394+ if not pendingValues.IsEmpty then
395+ let untilAfterNextDispatch =
396+ getTimeUntilNextDispatch()
397+ + match buffer with
398+ | Some value -> value
399+ | None -> System.TimeSpan.Zero
400+
401+ if untilAfterNextDispatch > System.TimeSpan.Zero then
402+ do ! Async.Sleep( untilAfterNextDispatch)
403+ })
404+
405+ // return both the factory and the await helper
406+ factory, awaitNextDispatch
0 commit comments