Class: Philiprehberger::Debounce::Throttler

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/debounce/throttler.rb

Overview

Limits execution to at most once per interval.

Unlike Debouncer, which delays until calls stop, Throttler guarantees a maximum execution frequency regardless of how often #call is invoked.

Examples:

throttler = Philiprehberger::Debounce.throttle(interval: 1.0) { |x| puts x }
10.times { throttler.call("hi") }  # executes at most once per second

Instance Method Summary collapse

Constructor Details

#initialize(interval:, leading: true, trailing: false, on_execute: nil, on_cancel: nil, on_flush: nil, on_error: nil, &block) ⇒ Throttler

Returns a new instance of Throttler.

Parameters:

  • interval (Float)

    minimum time between executions in seconds

  • leading (Boolean) (defaults to: true)

    fire on the leading edge

  • trailing (Boolean) (defaults to: false)

    fire on the trailing edge

  • on_execute (Proc, nil) (defaults to: nil)

    callback after block executes, receives return value

  • on_cancel (Proc, nil) (defaults to: nil)

    callback when cancel is invoked

  • on_flush (Proc, nil) (defaults to: nil)

    callback when flush is invoked

  • block (Proc)

    the block to execute

Raises:

  • (ArgumentError)


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/philiprehberger/debounce/throttler.rb', line 21

def initialize(interval:, leading: true, trailing: false, on_execute: nil, on_cancel: nil, on_flush: nil, on_error: nil, &block)
  raise ArgumentError, 'block is required' unless block
  raise ArgumentError, 'interval must be positive' unless interval.positive?
  raise ArgumentError, 'at least one of leading or trailing must be true' if !leading && !trailing

  @interval = interval
  @leading = leading
  @trailing = trailing
  @on_execute = on_execute
  @on_cancel = on_cancel
  @on_flush = on_flush
  @on_error = on_error
  @block = block
  @mutex = Mutex.new
  @condition = ConditionVariable.new
  @last_args = nil
  @pending = false
  @trailing_scheduled = false
  @last_execution_time = nil
  @call_count = 0
  @execution_count = 0
  @last_result = nil
end

Instance Method Details

#call(*args) ⇒ void

This method returns an undefined value.

Invoke the throttler with optional arguments.

If enough time has elapsed since the last execution, the block runs immediately (leading edge). Otherwise the arguments are stored and the block fires at the end of the current interval (trailing edge).

Parameters:

  • args (Array)

    arguments forwarded to the block



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/philiprehberger/debounce/throttler.rb', line 53

def call(*args)
  @mutex.synchronize do
    @call_count += 1
    now = monotonic_now
    @last_args = args
    @pending = true

    if @last_execution_time.nil? || (now - @last_execution_time) >= @interval
      # Enough time has passed — fire immediately if leading
      if @leading
        @last_execution_time = now
        @pending = false
        execute(args)
      elsif @trailing
        schedule_trailing
      end
    elsif @trailing
      schedule_trailing
    end
  end
end

#cancelvoid

This method returns an undefined value.

Cancel any pending trailing execution.



78
79
80
81
82
83
84
85
86
# File 'lib/philiprehberger/debounce/throttler.rb', line 78

def cancel
  @mutex.synchronize do
    @pending = false
    @last_args = nil
    @condition.signal
  end

  invoke_callback(@on_cancel)
end

#flushvoid

This method returns an undefined value.

Execute the pending block immediately and cancel the trailing timer.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/philiprehberger/debounce/throttler.rb', line 91

def flush
  args = nil
  should_execute = false

  @mutex.synchronize do
    if @pending
      args = @last_args
      should_execute = true
      @pending = false
      @last_args = nil
      @last_execution_time = monotonic_now
      @condition.signal
    end
  end

  execute(args) if should_execute
  invoke_callback(@on_flush)
end

#last_resultObject?

Returns the result of the last block execution.

Returns:

  • (Object, nil)


142
143
144
# File 'lib/philiprehberger/debounce/throttler.rb', line 142

def last_result
  @mutex.synchronize { @last_result }
end

#metricsHash

Returns metrics about throttler usage.

Returns:

  • (Hash)

    call_count, execution_count, suppressed_count



129
130
131
132
133
134
135
136
137
# File 'lib/philiprehberger/debounce/throttler.rb', line 129

def metrics
  @mutex.synchronize do
    {
      call_count: @call_count,
      execution_count: @execution_count,
      suppressed_count: @call_count - @execution_count
    }
  end
end

#pending?Boolean

Whether there is a pending trailing execution.

Returns:

  • (Boolean)


113
114
115
# File 'lib/philiprehberger/debounce/throttler.rb', line 113

def pending?
  @mutex.synchronize { @pending }
end

#pending_argsArray?

Returns the arguments that would be passed to the next execution.

Returns:

  • (Array, nil)

    the pending arguments, or nil if not pending



120
121
122
123
124
# File 'lib/philiprehberger/debounce/throttler.rb', line 120

def pending_args
  @mutex.synchronize do
    @pending ? @last_args : nil
  end
end

#reset_metricsvoid

This method returns an undefined value.

Resets all metric counters to zero.



149
150
151
152
153
154
# File 'lib/philiprehberger/debounce/throttler.rb', line 149

def reset_metrics
  @mutex.synchronize do
    @call_count = 0
    @execution_count = 0
  end
end