Module: Farce::Abstract::Counter Abstract

Includes:
Value, Internal::Copyable, Internal::ValueSerialization, Internal::Inspect, Internal::MarshalSupport::Counter
Included in:
Counter, Local::Counter
Defined in:
lib/farce/abstract/counter.rb,
lib/farce/integrations/psych.rb

Overview

This module is abstract.

Common numeric interface and atomic operations for counters.

Note:

This is a module rather than a class so Counter can inherit directly from the native counter and avoid delegation overhead.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Internal::ValueSerialization

#as_extended_json, #as_json, #to_bson, #to_bson_normalized_value, #to_cbor, #to_json, #to_msgpack

Methods included from Value

#blank?, #unwrap, #value

Methods included from Internal::Copyable

#duplicable?

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing ⇒ BasicObject (private)



189
# File 'lib/farce/abstract/counter.rb', line 189

def method_missing(...)              = value.public_send(...)

Class Method Details

.included(base) ⇒ BasicObject



15
16
17
18
# File 'lib/farce/abstract/counter.rb', line 15

def self.included(base)
  Internal.prepare_mutable_numeric(base)
  super
end

Instance Method Details

#add(by = 1) ⇒ BasicObject



172
# File 'lib/farce/abstract/counter.rb', line 172

def add(...) = increment(...)

#decrement_if_above(floor) ⇒ Boolean

Decrement the counter by one if its current value is above the floor. The check and decrement happen atomically.

Parameters:

  • floor (Numeric, String, #to_int) —

    The lower bound, converted to an Integer.

Returns:

  • (Boolean) —

    true if the counter changed, otherwise false.



156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/farce/abstract/counter.rb', line 156

def decrement_if_above(floor) # rubocop:disable Naming/PredicateMethod
  floor   = Integer(floor)
  current = value

  Internal::Freeze.check(self)

  while current > floor
    return true if compare_and_set(current, current - 1)
    current = value
  end

  false
end

#increment_if_below(limit) ⇒ Boolean

Increment the counter by one if its current value is below the limit. The check and increment happen atomically.

Parameters:

  • limit (Numeric, String, #to_int) —

    The upper bound, converted to an Integer.

Returns:

  • (Boolean) —

    true if the counter changed, otherwise false.



138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/farce/abstract/counter.rb', line 138

def increment_if_below(limit) # rubocop:disable Naming/PredicateMethod
  limit   = Integer(limit)
  current = value

  Internal::Freeze.check(self)

  while current < limit
    return true if compare_and_set(current, current + 1)
    current = value
  end

  false
end

#reset ⇒ self

Reset the counter to its initial value.

Returns:

  • (self) —

    Returns self for chaining.



129
130
131
132
# File 'lib/farce/abstract/counter.rb', line 129

def reset
  self.value = initial
  self
end

#wait_until(timeout: nil) {|value| ... } ⇒ Integer?

Wait until a block condition matches the current value. One timeout budget covers all checks and waits. The block is not interrupted.

Parameters:

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Yield Parameters:

  • value (Integer) —

    the current value

Yield Returns:

  • (Boolean) —

    whether the value matches

Returns:

  • (Integer, nil) —

    the matching value, or nil on timeout

Raises:

  • (LocalJumpError) —

    if no block is given



50
# File 'lib/farce/abstract/counter.rb', line 50

def wait_until(timeout: nil, &) = Internal.wait_until(self, timeout:, &)

#wait_until_above(floor, timeout: nil) ⇒ Integer?

Wait until the value is strictly above the floor.

Parameters:

  • floor (Numeric, String, #to_int) —

    the lower bound, converted to an Integer

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Returns:

  • (Integer, nil) —

    the matching value, or nil on timeout



104
105
106
107
# File 'lib/farce/abstract/counter.rb', line 104

def wait_until_above(floor, timeout: nil)
  floor = Integer(floor)
  wait_until(timeout:) { |value| value > floor }
end

#wait_until_below(limit, timeout: nil) ⇒ Integer?

Wait until the value is strictly below the limit.

Parameters:

  • limit (Numeric, String, #to_int) —

    the upper bound, converted to an Integer

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Returns:

  • (Integer, nil) —

    the matching value, or nil on timeout



95
96
97
98
# File 'lib/farce/abstract/counter.rb', line 95

def wait_until_below(limit, timeout: nil)
  limit = Integer(limit)
  wait_until(timeout:) { |value| value < limit }
end

#wait_until_changed(expected, timeout: nil) { ... } ⇒ Integer, ...

Wait until the current value differs from the expected value.

Parameters:

  • expected (Integer) —

    the value to wait to change from

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Yields:

  • called when the timeout expires

Returns:

  • (Integer, BasicObject, nil) —

    the changed value or timeout fallback



32
33
34
35
36
37
38
39
40
41
# File 'lib/farce/abstract/counter.rb', line 32

def wait_until_changed(expected, timeout: nil)
  signal = change_signal
  Internal.with_timeout(timeout) do |_, deadline|
    observed = signal.generation
    current = value
    return current unless expected == current
    break unless signal.wait(observed, timeout: Internal.remaining_timeout(deadline))
  end
  yield if block_given?
end

#wait_until_match(object, timeout: nil) ⇒ Integer?

Wait until object === value is true.

Parameters:

  • object (#===) —

    the pattern to match

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Returns:

  • (Integer, nil) —

    the matching value, or nil on timeout



87
88
89
# File 'lib/farce/abstract/counter.rb', line 87

def wait_until_match(object, timeout: nil)
  wait_until(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality
end

#wait_until_value(object, timeout: nil) ⇒ Integer?

Wait until object == value is true. Counters compare integer values by equality.

Parameters:

  • object (BasicObject) —

    the value to compare with the current value

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Returns:

  • (Integer, nil) —

    the matching value, or nil on timeout



79
80
81
# File 'lib/farce/abstract/counter.rb', line 79

def wait_until_value(object, timeout: nil)
  wait_until(timeout:) { |value| object == value }
end

#wait_while(timeout: nil) {|value| ... } ⇒ Integer?

Wait while the block returns a truthy value.

Parameters:

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Yield Parameters:

  • value (Integer) —

    the current value

Yield Returns:

  • (BasicObject) —

    a truthy value to keep waiting, or nil or false to stop

Returns:

  • (Integer, nil) —

    the value when the condition becomes false, or nil on timeout

Raises:

  • (LocalJumpError) —

    if no block is given



58
59
60
61
# File 'lib/farce/abstract/counter.rb', line 58

def wait_while(timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  wait_until(timeout:) { |value| !yield(value) }
end

#wait_while_above(limit, timeout: nil) ⇒ Integer?

Wait while the value is strictly above the limit.

Parameters:

  • limit (Numeric, String, #to_int) —

    the upper bound, converted to an Integer

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Returns:

  • (Integer, nil) —

    the first value at or below the limit, or nil on timeout



113
114
115
116
# File 'lib/farce/abstract/counter.rb', line 113

def wait_while_above(limit, timeout: nil)
  limit = Integer(limit)
  wait_until(timeout:) { |value| value <= limit }
end

#wait_while_below(floor, timeout: nil) ⇒ Integer?

Wait while the value is strictly below the floor.

Parameters:

  • floor (Numeric, String, #to_int) —

    the lower bound, converted to an Integer

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Returns:

  • (Integer, nil) —

    the first value at or above the floor, or nil on timeout



122
123
124
125
# File 'lib/farce/abstract/counter.rb', line 122

def wait_while_below(floor, timeout: nil)
  floor = Integer(floor)
  wait_until(timeout:) { |value| value >= floor }
end

#wait_while_match(object, timeout: nil) ⇒ Integer?

Wait while object === value is true.

Parameters:

  • object (#===) —

    the pattern to stop matching

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Returns:

  • (Integer, nil) —

    the first nonmatching value, or nil on timeout



67
68
69
# File 'lib/farce/abstract/counter.rb', line 67

def wait_while_match(object, timeout: nil)
  wait_while(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality
end

#wait_while_value { ... } ⇒ Integer, ...

Wait until the current value differs from the expected value.

Parameters:

  • expected (Integer) —

    the value to wait to change from

  • timeout (Numeric, nil) —

    the total seconds available

Yields:

  • called when the timeout expires

Returns:

  • (Integer, BasicObject, nil) —

    the changed value or timeout fallback



72
# File 'lib/farce/abstract/counter.rb', line 72

def wait_while_value(...) = wait_until_changed(...)